diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt deleted file mode 100644 index 631bf2a4..00000000 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ /dev/null @@ -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? - 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 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 { - 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? = null, - selection: Pair>? = null, orderBy: String? = null, - signal: CancellationSignal? = null - ): Cursor { - return query( - false, - table, - columns, - selection?.first, - selection?.second, - null, - null, - orderBy, - null, - signal - ) - } -} diff --git a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt index 1f0b9aee..d7d1b831 100644 --- a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt +++ b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt @@ -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() 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 { diff --git a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt index ae5376f4..e3cfa1aa 100644 --- a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt @@ -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()) diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt index 40dcec0e..8ef47e46 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt @@ -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 { diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt index 9d5ca363..f6c87243 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt @@ -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() } diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt index e157e55c..38978791 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt @@ -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 { diff --git a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt index 101abdc7..43ea3567 100644 --- a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt @@ -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())