diff --git a/service/src/main/java/com/github/kr328/clash/service/data/Database.kt b/service/src/main/java/com/github/kr328/clash/service/data/Database.kt index a642398..ef25c8e 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/Database.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/Database.kt @@ -4,9 +4,7 @@ import android.content.Context import androidx.room.Room import androidx.room.RoomDatabase import com.github.kr328.clash.common.Global -import com.github.kr328.clash.service.data.DatabaseMigrations.VERSION_1_2 -import com.github.kr328.clash.service.data.DatabaseMigrations.VERSION_2_3 -import com.github.kr328.clash.service.data.DatabaseMigrations.VERSION_3_4 +import com.github.kr328.clash.service.data.migrations.MIGRATIONS import androidx.room.Database as DatabaseMetadata @DatabaseMetadata( @@ -26,7 +24,7 @@ abstract class Database : RoomDatabase() { context.applicationContext, Database::class.java, "clash-config" - ).addMigrations(VERSION_1_2, VERSION_2_3, VERSION_3_4).build() + ).addMigrations(*MIGRATIONS).build() } } } \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/DatabaseMigrations.kt b/service/src/main/java/com/github/kr328/clash/service/data/DatabaseMigrations.kt deleted file mode 100644 index 6da814c..0000000 --- a/service/src/main/java/com/github/kr328/clash/service/data/DatabaseMigrations.kt +++ /dev/null @@ -1,327 +0,0 @@ -package com.github.kr328.clash.service.data - -import android.content.ContentValues -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT -import android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE -import androidx.core.app.NotificationManagerCompat -import androidx.core.content.edit -import androidx.core.database.getStringOrNull -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase -import com.github.kr328.clash.common.Global -import com.github.kr328.clash.common.utils.Log -import com.github.kr328.clash.service.Constants -import com.github.kr328.clash.service.settings.ServiceSettings -import com.github.kr328.clash.service.util.resolveBaseDir -import com.github.kr328.clash.service.util.resolveProfileFile -import java.io.File - -object DatabaseMigrations { - val VERSION_1_2 = object : Migration(1, 2) { - private fun process(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE profiles RENAME TO _profiles") - database.execSQL("ALTER TABLE profile_select_proxies RENAME TO _profile_select_proxies") - - database.execSQL("CREATE TABLE IF NOT EXISTS `profiles` (`name` TEXT NOT NULL, `type` INTEGER NOT NULL, `uri` TEXT NOT NULL, `source` TEXT, `active` INTEGER NOT NULL, `last_update` INTEGER NOT NULL, `update_interval` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))") - database.execSQL("CREATE TABLE IF NOT EXISTS `profile_select_proxies` (`profile_id` INTEGER NOT NULL, `proxy` TEXT NOT NULL, `selected` TEXT NOT NULL, PRIMARY KEY(`profile_id`, `proxy`), FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )") - - database.query("SELECT name, token, file, active, last_update, id FROM _profiles") - .use { cursor -> - - Global.application.filesDir.resolve(Constants.CLASH_DIR).listFiles()?.forEach { - it.deleteRecursively() - } - - cursor.moveToFirst() - while (!cursor.isAfterLast) { - // old - // name, token, file, active, last_update, id - 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) - val id = cursor.getLong(5) - - // new - // name, type, uri, source, active, last_update, update_interval, id - val type = when { - token.startsWith("url") -> ProfileEntity.TYPE_URL - token.startsWith("file") -> ProfileEntity.TYPE_FILE - else -> ProfileEntity.TYPE_UNKNOWN - } - - File(file).renameTo(Global.application.resolveProfileFile(id)) - Global.application.resolveBaseDir(id).mkdirs() - - database.insert("profiles", - CONFLICT_ABORT, - ContentValues().apply { - put("name", name) - put("type", type) - put("uri", token.removePrefix("url|").removePrefix("file|")) - putNull("source") - put("active", active) - put("last_update", lastUpdate) - put("update_interval", 0) - put("id", id) - }) - - cursor.moveToNext() - } - } - - database.query("SELECT profile_id, proxy, selected FROM _profile_select_proxies ORDER BY id") - .use { cursor -> - cursor.moveToFirst() - while (!cursor.isAfterLast) { - // old - // profile_id, proxy, selected, id - val profileId = cursor.getLong(0) - val proxy: String = cursor.getString(1) - val selected = cursor.getString(2) - - // new - // profile_id, proxy, selected - - database.insert("profile_select_proxies", - CONFLICT_REPLACE, - ContentValues().apply { - put("profile_id", profileId) - put("proxy", proxy) - put("selected", selected) - }) - - cursor.moveToNext() - } - } - - database.execSQL("DROP TABLE IF EXISTS _profiles") - database.execSQL("DROP TABLE IF EXISTS _profile_select_proxies") - - // Migration settings - val oldSettings = Global.application - .getSharedPreferences("clash_service", Context.MODE_PRIVATE) - val newSettings = ServiceSettings( - Global.application - .getSharedPreferences(Constants.SERVICE_SETTING_FILE_NAME, Context.MODE_PRIVATE) - ) - - val accessMode = oldSettings - .getInt("key_access_control_mode", 0) - val accessPackages = oldSettings - .getStringSet("ley_access_control_apps", emptySet())!! // just typo :) - val dnsHijack = oldSettings - .getBoolean("key_dns_hijacking_enabled", true) - val bypassPrivate = oldSettings - .getBoolean("key_bypass_private_network", true) - - oldSettings.edit { - clear() - } - - newSettings.commit { - val newAccessMode = when (accessMode) { - 0 -> ServiceSettings.ACCESS_CONTROL_MODE_ALL - 1 -> ServiceSettings.ACCESS_CONTROL_MODE_WHITELIST - 2 -> ServiceSettings.ACCESS_CONTROL_MODE_BLACKLIST - else -> ServiceSettings.ACCESS_CONTROL_MODE_ALL - } - - put(ServiceSettings.ACCESS_CONTROL_MODE, newAccessMode) - put(ServiceSettings.ACCESS_CONTROL_PACKAGES, accessPackages) - put(ServiceSettings.DNS_HIJACKING, dnsHijack) - put(ServiceSettings.BYPASS_PRIVATE_NETWORK, bypassPrivate) - } - } - - override fun migrate(database: SupportSQLiteDatabase) { - try { - process(database) - Log.i("Database Migrated 1 -> 2") - } catch (e: Exception) { - Log.e("Migration failure", e) - } - } - } - - val VERSION_2_3 = object : Migration(2, 3) { - override fun migrate(database: SupportSQLiteDatabase) { - try { - database.execSQL("ALTER TABLE profile_select_proxies RENAME TO _selected_proxies") - database.execSQL("ALTER TABLE profiles RENAME TO _profiles") - - database.execSQL("CREATE TABLE IF NOT EXISTS `profiles` (`name` TEXT NOT NULL, `type` INTEGER NOT NULL, `uri` TEXT NOT NULL, `source` TEXT, `active` INTEGER NOT NULL, `interval` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))") - database.execSQL("CREATE TABLE IF NOT EXISTS `selected_proxies` (`profile_id` INTEGER NOT NULL, `proxy` TEXT NOT NULL, `selected` TEXT NOT NULL, PRIMARY KEY(`profile_id`, `proxy`), FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )") - - database.query("SELECT name, type, uri, source, active, update_interval, id FROM _profiles") - .use { cursor -> - cursor.moveToFirst() - while (!cursor.isAfterLast) { - // old - // name, type, uri, source, active, last_update, update_interval(seconds), id - // new - // name, type, uri, source, active, interval(millis seconds), id - val name = cursor.getString(0) - val type = cursor.getInt(1) - val uri = cursor.getString(2) - val source = cursor.getStringOrNull(3) - val active = cursor.getInt(4) - val interval = cursor.getLong(5) - val id = cursor.getLong(6) - - database.insert("profiles", - CONFLICT_ABORT, - ContentValues().apply { - put("name", name) - put("type", type) - put("uri", uri) - put("source", source) - put("active", active) - put("interval", interval * 1000) - put("id", id) - }) - - cursor.moveToNext() - } - } - - database.query("SELECT profile_id, proxy, selected FROM _selected_proxies") - .use { cursor -> - cursor.moveToFirst() - while (!cursor.isAfterLast) { - // just copy - // profile_id, proxy, selected - val profileId = cursor.getLong(0) - val proxy = cursor.getString(1) - val selected = cursor.getString(2) - - database.insert("selected_proxies", - CONFLICT_REPLACE, - ContentValues().apply { - put("profile_id", profileId) - put("proxy", proxy) - put("selected", selected) - }) - - cursor.moveToNext() - } - } - - database.execSQL("DROP TABLE IF EXISTS _profiles") - database.execSQL("DROP TABLE IF EXISTS _selected_proxies") - - val uiSp = Global.application - .getSharedPreferences("ui", Context.MODE_PRIVATE) - val srvSp = Global.application - .getSharedPreferences("service", Context.MODE_PRIVATE) - - srvSp.edit { - putBoolean("enable_vpn", uiSp.getBoolean("enable_vpn", true)) - } - - NotificationManagerCompat.from(Global.application).apply { - deleteNotificationChannel("profile_service_status") - deleteNotificationChannel("profile_service_result") - } - - Log.i("Database Migrated 2 -> 3") - } catch (e: Exception) { - Log.e("Migration failure", e) - } - } - } - - val VERSION_3_4 = object : Migration(3, 4) { - override fun migrate(database: SupportSQLiteDatabase) { - try { - val profiles = mutableListOf() - try { - database.query("SELECT name, type, uri, source, active, interval, id FROM profiles") - .use { cursor -> - cursor.moveToFirst() - while (!cursor.isAfterLast) { - // old - // name, type, uri, source, active, last_update, update_interval(seconds), id - // new - // name, type, uri, source, active, interval(millis seconds), id - val name = cursor.getString(0) - val type = cursor.getInt(1) - val uri = cursor.getString(2) - val source = cursor.getStringOrNull(3) - val active = cursor.getInt(4) - val interval = cursor.getLong(5) - val id = cursor.getLong(6) - - profiles.add(ProfileEntity(name, type, uri, source, active != 0, interval, id)) - - cursor.moveToNext() - } - } - } - catch (e: Exception) { - Log.w("Query old data failure", e) - } - - val selectedProxies = mutableListOf() - - try { - database.query("SELECT profile_id, proxy, selected FROM selected_proxies") - .use { cursor -> - cursor.moveToFirst() - while (!cursor.isAfterLast) { - // just copy - // profile_id, proxy, selected - val profileId = cursor.getLong(0) - val proxy = cursor.getString(1) - val selected = cursor.getString(2) - - selectedProxies.add(SelectedProxyEntity(profileId, proxy, selected)) - - cursor.moveToNext() - } - } - } - catch (e: Exception) { - Log.w("Query old data failure", e) - } - - database.execSQL("DROP TABLE IF EXISTS profile_select_proxies") - database.execSQL("DROP TABLE IF EXISTS selected_proxies") - database.execSQL("DROP TABLE IF EXISTS profiles") - database.execSQL("DROP TABLE IF EXISTS _profile_select_proxies") - database.execSQL("DROP TABLE IF EXISTS _selected_proxies") - database.execSQL("DROP TABLE IF EXISTS _profiles") - - database.execSQL("CREATE TABLE IF NOT EXISTS `profiles` (`name` TEXT NOT NULL, `type` INTEGER NOT NULL, `uri` TEXT NOT NULL, `source` TEXT, `active` INTEGER NOT NULL, `interval` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))") - database.execSQL("CREATE TABLE IF NOT EXISTS `selected_proxies` (`profile_id` INTEGER NOT NULL, `proxy` TEXT NOT NULL, `selected` TEXT NOT NULL, PRIMARY KEY(`profile_id`, `proxy`), FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )") - - profiles.forEach { - database.insert("profiles", CONFLICT_ABORT, ContentValues().apply { - put("name", it.name) - put("type", it.type) - put("uri", it.uri) - put("source", it.source) - put("active", it.active) - put("interval", it.interval) - put("id", it.id) - }) - } - - selectedProxies.forEach { - database.insert("selected_proxies", CONFLICT_REPLACE, ContentValues().apply { - put("profile_id", it.profileId) - put("proxy", it.proxy) - put("selected", it.selected) - }) - } - - Log.i("Database Migrated 3 -> 4") - } catch (e: Exception) { - Log.e("Migration failure", e) - } - } - } -} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration12.kt b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration12.kt new file mode 100644 index 0000000..c77a7d1 --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration12.kt @@ -0,0 +1,144 @@ +package com.github.kr328.clash.service.data.migrations + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import androidx.core.content.edit +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.github.kr328.clash.common.Global +import com.github.kr328.clash.common.utils.Log +import com.github.kr328.clash.service.Constants +import com.github.kr328.clash.service.data.ProfileEntity +import com.github.kr328.clash.service.settings.ServiceSettings +import com.github.kr328.clash.service.util.resolveBaseDir +import com.github.kr328.clash.service.util.resolveProfileFile +import java.io.File + +object Migration12: Migration(1, 2) { + private fun process(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE profiles RENAME TO _profiles") + database.execSQL("ALTER TABLE profile_select_proxies RENAME TO _profile_select_proxies") + + database.execSQL("CREATE TABLE IF NOT EXISTS `profiles` (`name` TEXT NOT NULL, `type` INTEGER NOT NULL, `uri` TEXT NOT NULL, `source` TEXT, `active` INTEGER NOT NULL, `last_update` INTEGER NOT NULL, `update_interval` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `profile_select_proxies` (`profile_id` INTEGER NOT NULL, `proxy` TEXT NOT NULL, `selected` TEXT NOT NULL, PRIMARY KEY(`profile_id`, `proxy`), FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )") + + database.query("SELECT name, token, file, active, last_update, id FROM _profiles") + .use { cursor -> + + Global.application.filesDir.resolve(Constants.CLASH_DIR).listFiles()?.forEach { + it.deleteRecursively() + } + + cursor.moveToFirst() + while (!cursor.isAfterLast) { + // old + // name, token, file, active, last_update, id + 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) + val id = cursor.getLong(5) + + // new + // name, type, uri, source, active, last_update, update_interval, id + val type = when { + token.startsWith("url") -> ProfileEntity.TYPE_URL + token.startsWith("file") -> ProfileEntity.TYPE_FILE + else -> ProfileEntity.TYPE_UNKNOWN + } + + File(file).renameTo(Global.application.resolveProfileFile(id)) + Global.application.resolveBaseDir(id).mkdirs() + + database.insert("profiles", + SQLiteDatabase.CONFLICT_ABORT, + ContentValues().apply { + put("name", name) + put("type", type) + put("uri", token.removePrefix("url|").removePrefix("file|")) + putNull("source") + put("active", active) + put("last_update", lastUpdate) + put("update_interval", 0) + put("id", id) + }) + + cursor.moveToNext() + } + } + + database.query("SELECT profile_id, proxy, selected FROM _profile_select_proxies ORDER BY id") + .use { cursor -> + cursor.moveToFirst() + while (!cursor.isAfterLast) { + // old + // profile_id, proxy, selected, id + val profileId = cursor.getLong(0) + val proxy: String = cursor.getString(1) + val selected = cursor.getString(2) + + // new + // profile_id, proxy, selected + + database.insert("profile_select_proxies", + SQLiteDatabase.CONFLICT_REPLACE, + ContentValues().apply { + put("profile_id", profileId) + put("proxy", proxy) + put("selected", selected) + }) + + cursor.moveToNext() + } + } + + database.execSQL("DROP TABLE IF EXISTS _profiles") + database.execSQL("DROP TABLE IF EXISTS _profile_select_proxies") + + // Migration settings + val oldSettings = Global.application + .getSharedPreferences("clash_service", Context.MODE_PRIVATE) + val newSettings = ServiceSettings( + Global.application + .getSharedPreferences(Constants.SERVICE_SETTING_FILE_NAME, Context.MODE_PRIVATE) + ) + + val accessMode = oldSettings + .getInt("key_access_control_mode", 0) + val accessPackages = oldSettings + .getStringSet("ley_access_control_apps", emptySet())!! // just typo :) + val dnsHijack = oldSettings + .getBoolean("key_dns_hijacking_enabled", true) + val bypassPrivate = oldSettings + .getBoolean("key_bypass_private_network", true) + + oldSettings.edit { + clear() + } + + newSettings.commit { + val newAccessMode = when (accessMode) { + 0 -> ServiceSettings.ACCESS_CONTROL_MODE_ALL + 1 -> ServiceSettings.ACCESS_CONTROL_MODE_WHITELIST + 2 -> ServiceSettings.ACCESS_CONTROL_MODE_BLACKLIST + else -> ServiceSettings.ACCESS_CONTROL_MODE_ALL + } + + put(ServiceSettings.ACCESS_CONTROL_MODE, newAccessMode) + put(ServiceSettings.ACCESS_CONTROL_PACKAGES, accessPackages) + put(ServiceSettings.DNS_HIJACKING, dnsHijack) + put(ServiceSettings.BYPASS_PRIVATE_NETWORK, bypassPrivate) + } + } + + override fun migrate(database: SupportSQLiteDatabase) { + try { + process(database) + Log.i("Database Migrated 1 -> 2") + } catch (e: Exception) { + Log.e("Migration failure", e) + } + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration23.kt b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration23.kt new file mode 100644 index 0000000..600c4ee --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration23.kt @@ -0,0 +1,99 @@ +package com.github.kr328.clash.service.data.migrations + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.edit +import androidx.core.database.getStringOrNull +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.github.kr328.clash.common.Global +import com.github.kr328.clash.common.utils.Log + +object Migration23: Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + try { + database.execSQL("ALTER TABLE profile_select_proxies RENAME TO _selected_proxies") + database.execSQL("ALTER TABLE profiles RENAME TO _profiles") + + database.execSQL("CREATE TABLE IF NOT EXISTS `profiles` (`name` TEXT NOT NULL, `type` INTEGER NOT NULL, `uri` TEXT NOT NULL, `source` TEXT, `active` INTEGER NOT NULL, `interval` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `selected_proxies` (`profile_id` INTEGER NOT NULL, `proxy` TEXT NOT NULL, `selected` TEXT NOT NULL, PRIMARY KEY(`profile_id`, `proxy`), FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )") + + database.query("SELECT name, type, uri, source, active, update_interval, id FROM _profiles") + .use { cursor -> + cursor.moveToFirst() + while (!cursor.isAfterLast) { + // old + // name, type, uri, source, active, last_update, update_interval(seconds), id + // new + // name, type, uri, source, active, interval(millis seconds), id + val name = cursor.getString(0) + val type = cursor.getInt(1) + val uri = cursor.getString(2) + val source = cursor.getStringOrNull(3) + val active = cursor.getInt(4) + val interval = cursor.getLong(5) + val id = cursor.getLong(6) + + database.insert("profiles", + SQLiteDatabase.CONFLICT_ABORT, + ContentValues().apply { + put("name", name) + put("type", type) + put("uri", uri) + put("source", source) + put("active", active) + put("interval", interval * 1000) + put("id", id) + }) + + cursor.moveToNext() + } + } + + database.query("SELECT profile_id, proxy, selected FROM _selected_proxies") + .use { cursor -> + cursor.moveToFirst() + while (!cursor.isAfterLast) { + // just copy + // profile_id, proxy, selected + val profileId = cursor.getLong(0) + val proxy = cursor.getString(1) + val selected = cursor.getString(2) + + database.insert("selected_proxies", + SQLiteDatabase.CONFLICT_REPLACE, + ContentValues().apply { + put("profile_id", profileId) + put("proxy", proxy) + put("selected", selected) + }) + + cursor.moveToNext() + } + } + + database.execSQL("DROP TABLE IF EXISTS _profiles") + database.execSQL("DROP TABLE IF EXISTS _selected_proxies") + + val uiSp = Global.application + .getSharedPreferences("ui", Context.MODE_PRIVATE) + val srvSp = Global.application + .getSharedPreferences("service", Context.MODE_PRIVATE) + + srvSp.edit { + putBoolean("enable_vpn", uiSp.getBoolean("enable_vpn", true)) + } + + NotificationManagerCompat.from(Global.application).apply { + deleteNotificationChannel("profile_service_status") + deleteNotificationChannel("profile_service_result") + } + + Log.i("Database Migrated 2 -> 3") + } catch (e: Exception) { + Log.e("Migration failure", e) + } + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration34.kt b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration34.kt new file mode 100644 index 0000000..1fb66ac --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migration34.kt @@ -0,0 +1,113 @@ +package com.github.kr328.clash.service.data.migrations + +import android.content.ContentValues +import android.database.sqlite.SQLiteDatabase +import androidx.core.database.getStringOrNull +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.github.kr328.clash.common.utils.Log +import com.github.kr328.clash.service.data.ProfileEntity +import com.github.kr328.clash.service.data.SelectedProxyEntity + +object Migration34: Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + try { + val profiles = mutableListOf() + try { + database.query("SELECT name, type, uri, source, active, interval, id FROM profiles") + .use { cursor -> + cursor.moveToFirst() + while (!cursor.isAfterLast) { + // old + // name, type, uri, source, active, last_update, update_interval(seconds), id + // new + // name, type, uri, source, active, interval(millis seconds), id + val name = cursor.getString(0) + val type = cursor.getInt(1) + val uri = cursor.getString(2) + val source = cursor.getStringOrNull(3) + val active = cursor.getInt(4) + val interval = cursor.getLong(5) + val id = cursor.getLong(6) + + profiles.add(ProfileEntity(name, type, uri, source, active != 0, interval, id)) + + cursor.moveToNext() + } + } + } + catch (e: Exception) { + Log.w("Query old data failure", e) + } + + val selectedProxies = mutableListOf() + + try { + database.query("SELECT profile_id, proxy, selected FROM selected_proxies") + .use { cursor -> + cursor.moveToFirst() + while (!cursor.isAfterLast) { + // just copy + // profile_id, proxy, selected + val profileId = cursor.getLong(0) + val proxy = cursor.getString(1) + val selected = cursor.getString(2) + + selectedProxies.add(SelectedProxyEntity(profileId, proxy, selected)) + + cursor.moveToNext() + } + } + } + catch (e: Exception) { + Log.w("Query old data failure", e) + } + + // Clean up database + runCatching { + database.execSQL("DROP TABLE IF EXISTS profile_select_proxies") + database.execSQL("DROP TABLE IF EXISTS selected_proxies") + database.execSQL("DROP TABLE IF EXISTS profiles") + database.execSQL("DROP TABLE IF EXISTS _profile_select_proxies") + database.execSQL("DROP TABLE IF EXISTS _selected_proxies") + database.execSQL("DROP TABLE IF EXISTS _profiles") + } + runCatching { + database.execSQL("DROP TABLE IF EXISTS profile_select_proxies") + database.execSQL("DROP TABLE IF EXISTS selected_proxies") + database.execSQL("DROP TABLE IF EXISTS profiles") + database.execSQL("DROP TABLE IF EXISTS _profile_select_proxies") + database.execSQL("DROP TABLE IF EXISTS _selected_proxies") + database.execSQL("DROP TABLE IF EXISTS _profiles") + } + + database.execSQL("CREATE TABLE IF NOT EXISTS `profiles` (`name` TEXT NOT NULL, `type` INTEGER NOT NULL, `uri` TEXT NOT NULL, `source` TEXT, `active` INTEGER NOT NULL, `interval` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))") + database.execSQL("CREATE TABLE IF NOT EXISTS `selected_proxies` (`profile_id` INTEGER NOT NULL, `proxy` TEXT NOT NULL, `selected` TEXT NOT NULL, PRIMARY KEY(`profile_id`, `proxy`), FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )") + + profiles.forEach { + database.insert("profiles", SQLiteDatabase.CONFLICT_ABORT, ContentValues().apply { + put("name", it.name) + put("type", it.type) + put("uri", it.uri) + put("source", it.source) + put("active", it.active) + put("interval", it.interval) + put("id", it.id) + }) + } + + selectedProxies.forEach { + database.insert("selected_proxies", + SQLiteDatabase.CONFLICT_REPLACE, ContentValues().apply { + put("profile_id", it.profileId) + put("proxy", it.proxy) + put("selected", it.selected) + }) + } + + Log.i("Database Migrated 3 -> 4") + } catch (e: Exception) { + Log.e("Migration failure", e) + } + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migrations.kt b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migrations.kt new file mode 100644 index 0000000..ce53f5d --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migrations.kt @@ -0,0 +1,5 @@ +package com.github.kr328.clash.service.data.migrations + +import androidx.room.migration.Migration + +val MIGRATIONS: Array = arrayOf(Migration12, Migration23, Migration34) \ No newline at end of file