Update: Remove Database class (4.8/5 in replacing SQLite with Room)

This commit is contained in:
machiav3lli 2021-11-10 00:57:24 +01:00
parent f966630e8a
commit 501e46d657
7 changed files with 7 additions and 331 deletions

View File

@ -1,318 +0,0 @@
package com.looker.droidify.database
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.os.CancellationSignal
import com.looker.droidify.entity.Repository
import com.looker.droidify.utility.extension.android.asSequence
import com.looker.droidify.utility.extension.android.firstOrNull
import io.reactivex.rxjava3.core.Observable
object Database {
fun init(context: Context): Boolean {
val helper = Helper(context)
db = helper.writableDatabase
if (helper.created) {
for (repository in Repository.defaultRepositories) {
//RepositoryAdapter.put(repository)
}
}
return helper.created || helper.updated
}
private lateinit var db: SQLiteDatabase
// not needed with Room
private interface Table {
val memory: Boolean
val innerName: String
val createTable: String
val createIndex: String?
get() = null
val databasePrefix: String
get() = if (memory) "memory." else ""
val name: String
get() = "$databasePrefix$innerName"
fun formatCreateTable(name: String): String {
return "CREATE TABLE $name (${QueryBuilder.trimQuery(createTable)})"
}
val createIndexPairFormatted: Pair<String, String>?
get() = createIndex?.let {
Pair(
"CREATE INDEX ${innerName}_index ON $innerName ($it)",
"CREATE INDEX ${name}_index ON $innerName ($it)"
)
}
}
private object Schema {
// implemented
object Repository : Table {
const val ROW_ID = "_id"
const val ROW_ENABLED = "enabled"
const val ROW_DELETED = "deleted"
const val ROW_DATA = "data"
override val memory = false
override val innerName = "repository"
override val createTable = """
$ROW_ID INTEGER PRIMARY KEY AUTOINCREMENT,
$ROW_ENABLED INTEGER NOT NULL,
$ROW_DELETED INTEGER NOT NULL,
$ROW_DATA BLOB NOT NULL
"""
}
// implemented
object Product : Table {
const val ROW_REPOSITORY_ID = "repository_id"
const val ROW_PACKAGE_NAME = "package_name"
const val ROW_NAME = "name"
const val ROW_SUMMARY = "summary"
const val ROW_DESCRIPTION = "description"
const val ROW_ADDED = "added"
const val ROW_UPDATED = "updated"
const val ROW_VERSION_CODE = "version_code"
const val ROW_SIGNATURES = "signatures"
const val ROW_COMPATIBLE = "compatible"
const val ROW_DATA = "data"
const val ROW_DATA_ITEM = "data_item"
override val memory = false
override val innerName = "product"
override val createTable = """
$ROW_REPOSITORY_ID INTEGER NOT NULL,
$ROW_PACKAGE_NAME TEXT NOT NULL,
$ROW_NAME TEXT NOT NULL,
$ROW_SUMMARY TEXT NOT NULL,
$ROW_DESCRIPTION TEXT NOT NULL,
$ROW_ADDED INTEGER NOT NULL,
$ROW_UPDATED INTEGER NOT NULL,
$ROW_VERSION_CODE INTEGER NOT NULL,
$ROW_SIGNATURES TEXT NOT NULL,
$ROW_COMPATIBLE INTEGER NOT NULL,
$ROW_DATA BLOB NOT NULL,
$ROW_DATA_ITEM BLOB NOT NULL,
PRIMARY KEY ($ROW_REPOSITORY_ID, $ROW_PACKAGE_NAME)
"""
override val createIndex = ROW_PACKAGE_NAME
}
// implemented
object Category : Table {
const val ROW_REPOSITORY_ID = "repository_id"
const val ROW_PACKAGE_NAME = "package_name"
const val ROW_NAME = "name"
override val memory = false
override val innerName = "category"
override val createTable = """
$ROW_REPOSITORY_ID INTEGER NOT NULL,
$ROW_PACKAGE_NAME TEXT NOT NULL,
$ROW_NAME TEXT NOT NULL,
PRIMARY KEY ($ROW_REPOSITORY_ID, $ROW_PACKAGE_NAME, $ROW_NAME)
"""
override val createIndex = "$ROW_PACKAGE_NAME, $ROW_NAME"
}
// implemented
object Installed : Table {
const val ROW_PACKAGE_NAME = "package_name"
const val ROW_VERSION = "version"
const val ROW_VERSION_CODE = "version_code"
const val ROW_SIGNATURE = "signature"
override val memory = true
override val innerName = "installed"
override val createTable = """
$ROW_PACKAGE_NAME TEXT PRIMARY KEY,
$ROW_VERSION TEXT NOT NULL,
$ROW_VERSION_CODE INTEGER NOT NULL,
$ROW_SIGNATURE TEXT NOT NULL
"""
}
// implemented
object Lock : Table {
const val ROW_PACKAGE_NAME = "package_name"
const val ROW_VERSION_CODE = "version_code"
override val memory = true
override val innerName = "lock"
override val createTable = """
$ROW_PACKAGE_NAME TEXT PRIMARY KEY,
$ROW_VERSION_CODE INTEGER NOT NULL
"""
}
}
// not needed remove after migration
private class Helper(context: Context) : SQLiteOpenHelper(context, "droidify", null, 1) {
var created = false
private set
var updated = false
private set
override fun onCreate(db: SQLiteDatabase) = Unit
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) =
onVersionChange(db)
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) =
onVersionChange(db)
private fun onVersionChange(db: SQLiteDatabase) {
handleTables(db, true, Schema.Product, Schema.Category)
this.updated = true
}
override fun onOpen(db: SQLiteDatabase) {
val create = handleTables(db, false, Schema.Repository)
val updated = handleTables(db, create, Schema.Product, Schema.Category)
db.execSQL("ATTACH DATABASE ':memory:' AS memory")
handleTables(db, false, Schema.Installed, Schema.Lock)
handleIndexes(
db,
Schema.Repository,
Schema.Product,
Schema.Category,
Schema.Installed,
Schema.Lock
)
dropOldTables(db, Schema.Repository, Schema.Product, Schema.Category)
this.created = this.created || create
this.updated = this.updated || create || updated
}
}
// not needed remove after migration
private fun handleTables(db: SQLiteDatabase, recreate: Boolean, vararg tables: Table): Boolean {
val shouldRecreate = recreate || tables.any {
val sql = db.query(
"${it.databasePrefix}sqlite_master", columns = arrayOf("sql"),
selection = Pair("type = ? AND name = ?", arrayOf("table", it.innerName))
)
.use { it.firstOrNull()?.getString(0) }.orEmpty()
it.formatCreateTable(it.innerName) != sql
}
return shouldRecreate && run {
val shouldVacuum = tables.map {
db.execSQL("DROP TABLE IF EXISTS ${it.name}")
db.execSQL(it.formatCreateTable(it.name))
!it.memory
}
if (shouldVacuum.any { it } && !db.inTransaction()) {
db.execSQL("VACUUM")
}
true
}
}
// not needed remove after migration
private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) {
val shouldVacuum = tables.map {
val sqls = db.query(
"${it.databasePrefix}sqlite_master", columns = arrayOf("name", "sql"),
selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", it.innerName))
)
.use {
it.asSequence()
.mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } }
.toList()
}
.filter { !it.first.startsWith("sqlite_") }
val createIndexes = it.createIndexPairFormatted?.let { listOf(it) }.orEmpty()
createIndexes.map { it.first } != sqls.map { it.second } && run {
for (name in sqls.map { it.first }) {
db.execSQL("DROP INDEX IF EXISTS $name")
}
for (createIndexPair in createIndexes) {
db.execSQL(createIndexPair.second)
}
!it.memory
}
}
if (shouldVacuum.any { it } && !db.inTransaction()) {
db.execSQL("VACUUM")
}
}
// TODO not needed, remove after migration
private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) {
val tables = db.query(
"sqlite_master", columns = arrayOf("name"),
selection = Pair("type = ?", arrayOf("table"))
)
.use { it.asSequence().mapNotNull { it.getString(0) }.toList() }
.filter { !it.startsWith("sqlite_") && !it.startsWith("android_") }
.toSet() - neededTables.mapNotNull { if (it.memory) null else it.name }
if (tables.isNotEmpty()) {
for (table in tables) {
db.execSQL("DROP TABLE IF EXISTS $table")
}
if (!db.inTransaction()) {
db.execSQL("VACUUM")
}
}
}
sealed class Subject {
object Repositories : Subject()
data class Repository(val id: Long) : Subject()
object Products : Subject()
}
private val observers = mutableMapOf<Subject, MutableSet<() -> Unit>>()
// TODO not needed remove after migration (replaced by LiveData)
private fun dataObservable(subject: Subject): (Boolean, () -> Unit) -> Unit =
{ register, observer ->
synchronized(observers) {
val set = observers[subject] ?: run {
val set = mutableSetOf<() -> Unit>()
observers[subject] = set
set
}
if (register) {
set += observer
} else {
set -= observer
}
}
}
// TODO not needed remove after migration (replaced by LiveData)
fun observable(subject: Subject): Observable<Unit> {
return Observable.create {
val callback: () -> Unit = { it.onNext(Unit) }
val dataObservable = dataObservable(subject)
dataObservable(true, callback)
it.setCancellable { dataObservable(false, callback) }
}
}
private fun SQLiteDatabase.query(
table: String, columns: Array<String>? = null,
selection: Pair<String, Array<String>>? = null, orderBy: String? = null,
signal: CancellationSignal? = null
): Cursor {
return query(
false,
table,
columns,
selection?.first,
selection?.second,
null,
null,
orderBy,
null,
signal
)
}
}

View File

@ -3,7 +3,6 @@ package com.looker.droidify.index
import android.content.Context
import android.net.Uri
import com.looker.droidify.content.Cache
import com.looker.droidify.database.Database
import com.looker.droidify.database.DatabaseX
import com.looker.droidify.entity.Product
import com.looker.droidify.entity.Release
@ -66,7 +65,7 @@ object RepositoryUpdater {
db = DatabaseX.getInstance(context)
var lastDisabled = setOf<Long>()
Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle {
RxUtils.querySingle {

View File

@ -17,7 +17,6 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.looker.droidify.R
import com.looker.droidify.database.Database
import com.looker.droidify.databinding.EditRepositoryBinding
import com.looker.droidify.entity.Repository
import com.looker.droidify.network.Downloader
@ -240,7 +239,7 @@ class EditRepositoryFragment() : ScreenFragment() {
}
repositoriesDisposable = Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } }
.observeOn(AndroidSchedulers.mainThread())

View File

@ -18,7 +18,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.looker.droidify.R
import com.looker.droidify.content.ProductPreferences
import com.looker.droidify.database.Database
import com.looker.droidify.entity.*
import com.looker.droidify.installer.AppInstaller
import com.looker.droidify.service.Connection
@ -133,7 +132,7 @@ class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks {
var first = true
productDisposable = Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle {
RxUtils.querySingle {

View File

@ -10,7 +10,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.looker.droidify.R
import com.looker.droidify.database.CursorOwner
import com.looker.droidify.database.Database
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.utility.RxUtils
import com.looker.droidify.widget.RecyclerFastScroller
@ -108,7 +107,7 @@ class ProductsFragment() : BaseFragment(), CursorOwner.Callback {
screenActivity.cursorOwner.attach(this, request)
repositoriesDisposable = Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } }
.map { it.asSequence().map { Pair(it.id, it) }.toMap() }

View File

@ -19,7 +19,6 @@ import androidx.viewpager2.widget.ViewPager2
import coil.load
import com.google.android.material.imageview.ShapeableImageView
import com.looker.droidify.R
import com.looker.droidify.database.Database
import com.looker.droidify.database.DatabaseX
import com.looker.droidify.entity.Product
import com.looker.droidify.entity.Repository
@ -134,7 +133,7 @@ class ScreenshotsFragment() : DialogFragment() {
var restored = false
productDisposable = Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle {
RxUtils.querySingle {

View File

@ -16,7 +16,6 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.textview.MaterialTextView
import com.looker.droidify.R
import com.looker.droidify.content.Preferences
import com.looker.droidify.database.Database
import com.looker.droidify.databinding.TabsToolbarBinding
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.service.Connection
@ -227,7 +226,7 @@ class TabsFragment : ScreenFragment() {
}
categoriesDisposable = Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle { RxUtils.querySingle { screenActivity.db.categoryDao.allNames } }
.observeOn(AndroidSchedulers.mainThread())
@ -238,7 +237,7 @@ class TabsFragment : ScreenFragment() {
)
}
repositoriesDisposable = Observable.just(Unit)
.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
//.concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava
.observeOn(Schedulers.io())
.flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } }
.observeOn(AndroidSchedulers.mainThread())