Fix SQLiteDatabaseLockedException
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.database
|
||||
|
||||
import android.database.sqlite.SQLiteDatabaseLockedException
|
||||
import com.j256.ormlite.dao.Dao
|
||||
import com.j256.ormlite.support.ConnectionSource
|
||||
import com.j256.ormlite.table.TableUtils
|
||||
import java.sql.SQLException
|
||||
|
||||
private val Throwable.ultimateCause: Throwable get() {
|
||||
var result = this
|
||||
while (true) {
|
||||
val cause = result.cause ?: return result
|
||||
result = cause
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
fun <T> safeWrapper(func: () -> T): T {
|
||||
while (true) {
|
||||
try {
|
||||
return func()
|
||||
} catch (e: SQLException) {
|
||||
if (e.ultimateCause !is SQLiteDatabaseLockedException) throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(SQLException::class)
|
||||
inline fun <reified T> ConnectionSource.createTableSafe() = safeWrapper { TableUtils.createTable(this, T::class.java) }
|
||||
@Throws(SQLException::class)
|
||||
fun <T, ID> Dao<T, ID>.queryAllSafe(): MutableList<T> = safeWrapper { queryForAll() }
|
||||
@Throws(SQLException::class)
|
||||
fun <T, ID> Dao<T, ID>.queryByIdSafe(id: ID?): T? = safeWrapper { queryForId(id) }
|
||||
@Throws(SQLException::class)
|
||||
fun <T, ID> Dao<T, ID>.updateSafe(data: T?): Int = safeWrapper { update(data) }
|
||||
@Throws(SQLException::class)
|
||||
fun <T, ID> Dao<T, ID>.replaceSafe(data: T?): Dao.CreateOrUpdateStatus? = safeWrapper { createOrUpdate(data) }
|
||||
@Throws(SQLException::class)
|
||||
fun <T, ID> Dao<T, ID>.deleteByIdSafe(id: ID?) = safeWrapper { deleteById(id) }
|
||||
@Throws(SQLException::class)
|
||||
fun <T, ID> Dao<T, ID>.executeSafe(statement: String?) = safeWrapper { executeRawNoArgs(statement) }
|
||||
@@ -37,18 +37,18 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
|
||||
|
||||
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
|
||||
TableUtils.createTable(connectionSource, Profile::class.java)
|
||||
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
|
||||
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) {
|
||||
connectionSource.createTableSafe<Profile>()
|
||||
connectionSource.createTableSafe<KeyValuePair>()
|
||||
}
|
||||
|
||||
private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
|
||||
private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) {
|
||||
TableUtils.dropTable<Profile, Int>(connectionSource, Profile::class.java, true)
|
||||
TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true)
|
||||
onCreate(database, connectionSource)
|
||||
}
|
||||
|
||||
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
|
||||
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource,
|
||||
oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 7) {
|
||||
recreate(database, connectionSource)
|
||||
@@ -57,38 +57,38 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
|
||||
|
||||
try {
|
||||
if (oldVersion < 8) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
|
||||
} else if (oldVersion < 19) {
|
||||
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
|
||||
profileDao.executeSafe("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
TableUtils.createTable(connectionSource, Profile::class.java)
|
||||
profileDao.executeRawNoArgs(
|
||||
profileDao.executeSafe("BEGIN TRANSACTION;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
connectionSource.createTableSafe<Profile>()
|
||||
profileDao.executeSafe(
|
||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route," +
|
||||
" proxyApps, bypass, udpdns, ipv6, individual) " +
|
||||
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global," +
|
||||
" bypass, udpdns, ipv6, individual FROM `tmp`;")
|
||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||
profileDao.executeRawNoArgs("COMMIT;")
|
||||
profileDao.executeSafe("DROP TABLE `tmp`;")
|
||||
profileDao.executeSafe("COMMIT;")
|
||||
} else if (oldVersion < 13) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN tx LONG;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN rx LONG;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
|
||||
}
|
||||
|
||||
if (oldVersion < 15) {
|
||||
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
|
||||
if (oldVersion >= 12) profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
|
||||
var i = 0L
|
||||
val apps by lazy { app.packageManager.getInstalledApplications(0) }
|
||||
for (profile in profileDao.queryForAll()) {
|
||||
for (profile in profileDao.queryAllSafe()) {
|
||||
if (oldVersion < 14) {
|
||||
val uidSet = profile.individual.split('|').filter(TextUtils::isDigitsOnly)
|
||||
.map(String::toInt).toSet()
|
||||
@@ -96,28 +96,28 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
|
||||
.joinToString("\n") { it.packageName }
|
||||
}
|
||||
profile.userOrder = i
|
||||
profileDao.update(profile)
|
||||
profileDao.updateSafe(profile)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
if (oldVersion < 16) {
|
||||
profileDao.executeRawNoArgs(
|
||||
profileDao.executeSafe(
|
||||
"UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
|
||||
}
|
||||
|
||||
if (oldVersion < 21) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
|
||||
}
|
||||
|
||||
if (oldVersion < 17) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
|
||||
} else if (oldVersion < 22) {
|
||||
// upgrade kcptun to SIP003 plugin
|
||||
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
TableUtils.createTable(connectionSource, Profile::class.java)
|
||||
profileDao.executeRawNoArgs(
|
||||
profileDao.executeSafe("BEGIN TRANSACTION;")
|
||||
profileDao.executeSafe("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
connectionSource.createTableSafe<Profile>()
|
||||
profileDao.executeSafe(
|
||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, " +
|
||||
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
|
||||
"plugin) " +
|
||||
@@ -125,17 +125,17 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
|
||||
"CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, route, " +
|
||||
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
|
||||
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
|
||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||
profileDao.executeRawNoArgs("COMMIT;")
|
||||
profileDao.executeSafe("DROP TABLE `tmp`;")
|
||||
profileDao.executeSafe("COMMIT;")
|
||||
}
|
||||
|
||||
if (oldVersion < 23) {
|
||||
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
|
||||
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
|
||||
profileDao.executeRawNoArgs("COMMIT;")
|
||||
profileDao.executeSafe("BEGIN TRANSACTION;")
|
||||
connectionSource.createTableSafe<KeyValuePair>()
|
||||
profileDao.executeSafe("COMMIT;")
|
||||
val old = PreferenceManager.getDefaultSharedPreferences(app)
|
||||
PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
|
||||
PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
|
||||
PublicDatabase.kvPairDao.replaceSafe(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
|
||||
PublicDatabase.kvPairDao.replaceSafe(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
|
||||
}
|
||||
|
||||
if (oldVersion < 25) {
|
||||
|
||||
@@ -48,10 +48,12 @@ object ProfileManager {
|
||||
profile.individual = oldProfile.individual
|
||||
profile.udpdns = oldProfile.udpdns
|
||||
}
|
||||
val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
|
||||
val last = safeWrapper {
|
||||
PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
|
||||
.selectRaw("MAX(userOrder)").prepareStatementString()).firstResult
|
||||
}
|
||||
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
|
||||
PrivateDatabase.profileDao.createOrUpdate(profile)
|
||||
PrivateDatabase.profileDao.replaceSafe(profile)
|
||||
ProfilesFragment.instance?.profilesAdapter?.add(profile)
|
||||
return profile
|
||||
}
|
||||
@@ -60,10 +62,10 @@ object ProfileManager {
|
||||
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
|
||||
*/
|
||||
@Throws(SQLException::class)
|
||||
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
|
||||
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.updateSafe(profile)
|
||||
|
||||
fun getProfile(id: Int): Profile? = try {
|
||||
PrivateDatabase.profileDao.queryForId(id)
|
||||
PrivateDatabase.profileDao.queryByIdSafe(id)
|
||||
} catch (ex: SQLException) {
|
||||
Log.e(TAG, "getProfile", ex)
|
||||
app.track(ex)
|
||||
@@ -72,13 +74,16 @@ object ProfileManager {
|
||||
|
||||
@Throws(SQLException::class)
|
||||
fun delProfile(id: Int) {
|
||||
PrivateDatabase.profileDao.deleteById(id)
|
||||
PrivateDatabase.profileDao.deleteByIdSafe(id)
|
||||
ProfilesFragment.instance?.profilesAdapter?.removeId(id)
|
||||
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
|
||||
}
|
||||
|
||||
fun getFirstProfile(): Profile? = try {
|
||||
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
|
||||
safeWrapper {
|
||||
PrivateDatabase.profileDao.query(
|
||||
PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
|
||||
}
|
||||
} catch (ex: SQLException) {
|
||||
Log.e(TAG, "getFirstProfile", ex)
|
||||
app.track(ex)
|
||||
@@ -86,7 +91,10 @@ object ProfileManager {
|
||||
}
|
||||
|
||||
fun getAllProfiles(): List<Profile>? = try {
|
||||
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
|
||||
safeWrapper {
|
||||
PrivateDatabase.profileDao.query(
|
||||
PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
|
||||
}
|
||||
} catch (ex: SQLException) {
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
app.track(ex)
|
||||
|
||||
@@ -34,20 +34,21 @@ object PublicDatabase : OrmLiteSqliteOpenHelper(app.deviceContext, Key.DB_PUBLIC
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
|
||||
|
||||
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
|
||||
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
|
||||
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) {
|
||||
connectionSource.createTableSafe<KeyValuePair>()
|
||||
}
|
||||
|
||||
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
|
||||
oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 1) {
|
||||
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
|
||||
safeWrapper {
|
||||
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
|
||||
Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query()
|
||||
.forEach { kvPairDao.createOrUpdate(it) }
|
||||
}.forEach { kvPairDao.replaceSafe(it) }
|
||||
}
|
||||
|
||||
if (oldVersion < 2) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString()))
|
||||
kvPairDao.replaceSafe(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-13
@@ -22,17 +22,20 @@ package com.github.shadowsocks.preference
|
||||
|
||||
import android.support.v7.preference.PreferenceDataStore
|
||||
import com.github.shadowsocks.database.KeyValuePair
|
||||
import com.github.shadowsocks.database.deleteByIdSafe
|
||||
import com.github.shadowsocks.database.queryByIdSafe
|
||||
import com.github.shadowsocks.database.replaceSafe
|
||||
import com.j256.ormlite.dao.Dao
|
||||
import java.util.HashSet
|
||||
|
||||
@Suppress("MemberVisibilityCanPrivate", "unused")
|
||||
open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() {
|
||||
fun getBoolean(key: String?) = kvPairDao.queryForId(key)?.boolean
|
||||
fun getFloat(key: String?) = kvPairDao.queryForId(key)?.float
|
||||
fun getInt(key: String?) = kvPairDao.queryForId(key)?.int
|
||||
fun getLong(key: String?) = kvPairDao.queryForId(key)?.long
|
||||
fun getString(key: String?) = kvPairDao.queryForId(key)?.string
|
||||
fun getStringSet(key: String?) = kvPairDao.queryForId(key)?.stringSet
|
||||
fun getBoolean(key: String?) = kvPairDao.queryByIdSafe(key)?.boolean
|
||||
fun getFloat(key: String?) = kvPairDao.queryByIdSafe(key)?.float
|
||||
fun getInt(key: String?) = kvPairDao.queryByIdSafe(key)?.int
|
||||
fun getLong(key: String?) = kvPairDao.queryByIdSafe(key)?.long
|
||||
fun getString(key: String?) = kvPairDao.queryByIdSafe(key)?.string
|
||||
fun getStringSet(key: String?) = kvPairDao.queryByIdSafe(key)?.stringSet
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
|
||||
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
|
||||
@@ -46,32 +49,32 @@ open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, S
|
||||
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
|
||||
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putFloat(key: String?, value: Float) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putInt(key: String?, value: Int) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putLong(key: String?, value: Long) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
|
||||
kvPairDao.replaceSafe(KeyValuePair(key).put(values))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
fun remove(key: String?) {
|
||||
kvPairDao.deleteById(key)
|
||||
kvPairDao.deleteByIdSafe(key)
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user