From cc8e7b6ea3be1707f42440084abcc8bea97b55e0 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:07:21 +0100 Subject: [PATCH] Clean up (Database.kt) --- .../com/looker/droidify/database/Database.kt | 535 +----------------- 1 file changed, 1 insertion(+), 534 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index 271181db..631bf2a4 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -1,24 +1,14 @@ package com.looker.droidify.database -import android.content.ContentValues import android.content.Context import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.os.CancellationSignal -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.looker.droidify.entity.InstalledItem -import com.looker.droidify.entity.Product -import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.utility.extension.android.asSequence import com.looker.droidify.utility.extension.android.firstOrNull -import com.looker.droidify.utility.extension.json.Json -import com.looker.droidify.utility.extension.json.parseDictionary -import com.looker.droidify.utility.extension.json.writeDictionary import io.reactivex.rxjava3.core.Observable -import java.io.ByteArrayOutputStream object Database { fun init(context: Context): Boolean { @@ -26,7 +16,7 @@ object Database { db = helper.writableDatabase if (helper.created) { for (repository in Repository.defaultRepositories) { - RepositoryAdapter.put(repository) + //RepositoryAdapter.put(repository) } } return helper.created || helper.updated @@ -160,12 +150,6 @@ object Database { $ROW_VERSION_CODE INTEGER NOT NULL """ } - - // TODO find a class to include them as constants - object Synthetic { - const val ROW_CAN_UPDATE = "can_update" - const val ROW_MATCH_RANK = "match_rank" - } } // not needed remove after migration @@ -313,26 +297,6 @@ object Database { } } - // TODO not needed remove after migration (replaced by LiveData) - private fun notifyChanged(vararg subjects: Subject) { - synchronized(observers) { - subjects.asSequence().mapNotNull { observers[it] }.flatten().forEach { it() } - } - } - - // TODO Done through inserts/replace of DAOs, only temporary still not finished - private fun SQLiteDatabase.insertOrReplace( - replace: Boolean, - table: String, - contentValues: ContentValues - ): Long { - return if (replace) replace(table, null, contentValues) else insert( - table, - null, - contentValues - ) - } - private fun SQLiteDatabase.query( table: String, columns: Array? = null, selection: Pair>? = null, orderBy: String? = null, @@ -351,501 +315,4 @@ object Database { signal ) } - - private fun Cursor.observable(subject: Subject): ObservableCursor { - return ObservableCursor(this, dataObservable(subject)) - } - - fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { - return Json.factory.createParser(this).use { it.parseDictionary(callback) } - } - - fun jsonGenerate(callback: (JsonGenerator) -> Unit): ByteArray { - val outputStream = ByteArrayOutputStream() - Json.factory.createGenerator(outputStream).use { it.writeDictionary(callback) } - return outputStream.toByteArray() - } - - // Partially done, only - object RepositoryAdapter { - // Done in put - internal fun putWithoutNotification(repository: Repository, shouldReplace: Boolean): Long { - return db.insertOrReplace(shouldReplace, Schema.Repository.name, ContentValues().apply { - if (shouldReplace) { - put(Schema.Repository.ROW_ID, repository.id) - } - put(Schema.Repository.ROW_ENABLED, if (repository.enabled) 1 else 0) - put(Schema.Repository.ROW_DELETED, 0) - put(Schema.Repository.ROW_DATA, jsonGenerate(repository::serialize)) - }) - } - - // Done - fun put(repository: Repository): Repository { - val shouldReplace = repository.id >= 0L - val newId = putWithoutNotification(repository, shouldReplace) - val id = if (shouldReplace) repository.id else newId - notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - return if (newId != repository.id) repository.copy(id = newId) else repository - } - - // Done - fun get(id: Long): Repository? { - return db.query( - Schema.Repository.name, - selection = Pair( - "${Schema.Repository.ROW_ID} = ? AND ${Schema.Repository.ROW_DELETED} == 0", - arrayOf(id.toString()) - ) - ) - .use { it.firstOrNull()?.let(::transform) } - } - - // Done - // MAYBE signal has to be considered - fun getAll(signal: CancellationSignal?): List { - return db.query( - Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal - ).use { it.asSequence().map(::transform).toList() } - } - - // Done Pair instead - // MAYBE signal has to be considered - fun getAllDisabledDeleted(signal: CancellationSignal?): Set> { - return db.query( - Schema.Repository.name, - columns = arrayOf(Schema.Repository.ROW_ID, Schema.Repository.ROW_DELETED), - selection = Pair( - "${Schema.Repository.ROW_ENABLED} == 0 OR ${Schema.Repository.ROW_DELETED} != 0", - emptyArray() - ), - signal = signal - ).use { - it.asSequence().map { - Pair( - it.getLong(it.getColumnIndex(Schema.Repository.ROW_ID)), - it.getInt(it.getColumnIndex(Schema.Repository.ROW_DELETED)) != 0 - ) - }.toSet() - } - } - - // Done - fun markAsDeleted(id: Long) { - db.update(Schema.Repository.name, ContentValues().apply { - put(Schema.Repository.ROW_DELETED, 1) - }, "${Schema.Repository.ROW_ID} = ?", arrayOf(id.toString())) - notifyChanged(Subject.Repositories, Subject.Repository(id), Subject.Products) - } - - // Done - fun cleanup(pairs: Set>) { - val result = pairs.windowed(10, 10, true).map { - val idsString = it.joinToString(separator = ", ") { it.first.toString() } - val productsCount = db.delete( - Schema.Product.name, - "${Schema.Product.ROW_REPOSITORY_ID} IN ($idsString)", null - ) - val categoriesCount = db.delete( - Schema.Category.name, - "${Schema.Category.ROW_REPOSITORY_ID} IN ($idsString)", null - ) - val deleteIdsString = it.asSequence().filter { it.second } - .joinToString(separator = ", ") { it.first.toString() } - if (deleteIdsString.isNotEmpty()) { - db.delete( - Schema.Repository.name, - "${Schema.Repository.ROW_ID} IN ($deleteIdsString)", - null - ) - } - productsCount != 0 || categoriesCount != 0 - } - if (result.any { it }) { - notifyChanged(Subject.Products) - } - } - - // get the cursor in the specific table. Unnecessary with Room - fun query(signal: CancellationSignal?): Cursor { - return db.query( - Schema.Repository.name, - selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()), - signal = signal - ).observable(Subject.Repositories) - } - - // Done - fun transform(cursor: Cursor): Repository { - return cursor.getBlob(cursor.getColumnIndex(Schema.Repository.ROW_DATA)) - .jsonParse { - Repository.deserialize(it).apply { - this.id = cursor.getLong(cursor.getColumnIndex(Schema.Repository.ROW_ID)) - } - } - } - } - - object ProductAdapter { - // Done - fun get(packageName: String, signal: CancellationSignal?): List { - return db.query( - Schema.Product.name, - columns = arrayOf( - Schema.Product.ROW_REPOSITORY_ID, - Schema.Product.ROW_DESCRIPTION, - Schema.Product.ROW_DATA - ), - selection = Pair("${Schema.Product.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal - ).use { it.asSequence().map(::transform).toList() } - } - - // Done - fun getCount(repositoryId: Long): Int { - return db.query( - Schema.Product.name, columns = arrayOf("COUNT (*)"), - selection = Pair( - "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repositoryId.toString()) - ) - ) - .use { it.firstOrNull()?.getInt(0) ?: 0 } - } - - // Done - fun query( - installed: Boolean, updates: Boolean, searchQuery: String, - section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? - ): Cursor { - val builder = QueryBuilder() - - val signatureMatches = """installed.${Schema.Installed.ROW_SIGNATURE} IS NOT NULL AND - product.${Schema.Product.ROW_SIGNATURES} LIKE ('%.' || installed.${Schema.Installed.ROW_SIGNATURE} || '.%') AND - product.${Schema.Product.ROW_SIGNATURES} != ''""" - - builder += """SELECT product.rowid AS _id, product.${Schema.Product.ROW_REPOSITORY_ID}, - product.${Schema.Product.ROW_PACKAGE_NAME}, product.${Schema.Product.ROW_NAME}, - product.${Schema.Product.ROW_SUMMARY}, installed.${Schema.Installed.ROW_VERSION}, - (COALESCE(lock.${Schema.Lock.ROW_VERSION_CODE}, -1) NOT IN (0, product.${Schema.Product.ROW_VERSION_CODE}) AND - product.${Schema.Product.ROW_COMPATIBLE} != 0 AND product.${Schema.Product.ROW_VERSION_CODE} > - COALESCE(installed.${Schema.Installed.ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) - AS ${Schema.Synthetic.ROW_CAN_UPDATE}, product.${Schema.Product.ROW_COMPATIBLE}, - product.${Schema.Product.ROW_DATA_ITEM},""" - - if (searchQuery.isNotEmpty()) { - builder += """(((product.${Schema.Product.ROW_NAME} LIKE ? OR - product.${Schema.Product.ROW_SUMMARY} LIKE ?) * 7) | - ((product.${Schema.Product.ROW_PACKAGE_NAME} LIKE ?) * 3) | - (product.${Schema.Product.ROW_DESCRIPTION} LIKE ?)) AS ${Schema.Synthetic.ROW_MATCH_RANK},""" - builder %= List(4) { "%$searchQuery%" } - } else { - builder += "0 AS ${Schema.Synthetic.ROW_MATCH_RANK}," - } - - builder += """MAX((product.${Schema.Product.ROW_COMPATIBLE} AND - (installed.${Schema.Installed.ROW_SIGNATURE} IS NULL OR $signatureMatches)) || - PRINTF('%016X', product.${Schema.Product.ROW_VERSION_CODE})) FROM ${Schema.Product.name} AS product""" - builder += """JOIN ${Schema.Repository.name} AS repository - ON product.${Schema.Product.ROW_REPOSITORY_ID} = repository.${Schema.Repository.ROW_ID}""" - builder += """LEFT JOIN ${Schema.Lock.name} AS lock - ON product.${Schema.Product.ROW_PACKAGE_NAME} = lock.${Schema.Lock.ROW_PACKAGE_NAME}""" - - if (!installed && !updates) { - builder += "LEFT" - } - builder += """JOIN ${Schema.Installed.name} AS installed - ON product.${Schema.Product.ROW_PACKAGE_NAME} = installed.${Schema.Installed.ROW_PACKAGE_NAME}""" - - if (section is ProductItem.Section.Category) { - builder += """JOIN ${Schema.Category.name} AS category - ON product.${Schema.Product.ROW_PACKAGE_NAME} = category.${Schema.Product.ROW_PACKAGE_NAME}""" - } - - builder += """WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND - repository.${Schema.Repository.ROW_DELETED} == 0""" - - if (section is ProductItem.Section.Category) { - builder += "AND category.${Schema.Category.ROW_NAME} = ?" - builder %= section.name - } else if (section is ProductItem.Section.Repository) { - builder += "AND product.${Schema.Product.ROW_REPOSITORY_ID} = ?" - builder %= section.id.toString() - } - - if (searchQuery.isNotEmpty()) { - builder += """AND ${Schema.Synthetic.ROW_MATCH_RANK} > 0""" - } - - builder += "GROUP BY product.${Schema.Product.ROW_PACKAGE_NAME} HAVING 1" - - if (updates) { - builder += "AND ${Schema.Synthetic.ROW_CAN_UPDATE}" - } - builder += "ORDER BY" - - if (searchQuery.isNotEmpty()) { - builder += """${Schema.Synthetic.ROW_MATCH_RANK} DESC,""" - } - - when (order) { - ProductItem.Order.NAME -> Unit - ProductItem.Order.DATE_ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC," - ProductItem.Order.LAST_UPDATE -> builder += "product.${Schema.Product.ROW_UPDATED} DESC," - }::class - builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC" - - return builder.query(db, signal).observable(Subject.Products) - } - - // Done - private fun transform(cursor: Cursor): Product { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA)) - .jsonParse { - Product.deserialize(it).apply { - this.repositoryId = cursor - .getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)) - this.description = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_DESCRIPTION)) - } - } - } - - // Done - fun transformItem(cursor: Cursor): ProductItem { - return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) - .jsonParse { - ProductItem.deserialize(it).apply { - this.repositoryId = cursor - .getLong(cursor.getColumnIndex(Schema.Product.ROW_REPOSITORY_ID)) - this.packageName = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_PACKAGE_NAME)) - this.name = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_NAME)) - this.summary = cursor - .getString(cursor.getColumnIndex(Schema.Product.ROW_SUMMARY)) - this.installedVersion = cursor - .getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)) - .orEmpty() - this.compatible = cursor - .getInt(cursor.getColumnIndex(Schema.Product.ROW_COMPATIBLE)) != 0 - this.canUpdate = cursor - .getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_CAN_UPDATE)) != 0 - this.matchRank = cursor - .getInt(cursor.getColumnIndex(Schema.Synthetic.ROW_MATCH_RANK)) - } - } - } - } - - // Done - object CategoryAdapter { - // Done - fun getAll(signal: CancellationSignal?): Set { - val builder = QueryBuilder() - - builder += """SELECT DISTINCT category.${Schema.Category.ROW_NAME} - FROM ${Schema.Category.name} AS category - JOIN ${Schema.Repository.name} AS repository - ON category.${Schema.Category.ROW_REPOSITORY_ID} = repository.${Schema.Repository.ROW_ID} - WHERE repository.${Schema.Repository.ROW_ENABLED} != 0 AND - repository.${Schema.Repository.ROW_DELETED} == 0""" - - return builder.query(db, signal).use { - it.asSequence() - .map { it.getString(it.getColumnIndex(Schema.Category.ROW_NAME)) }.toSet() - } - } - } - - // Done - object InstalledAdapter { - // Done - fun get(packageName: String, signal: CancellationSignal?): InstalledItem? { - return db.query( - Schema.Installed.name, - columns = arrayOf( - Schema.Installed.ROW_PACKAGE_NAME, Schema.Installed.ROW_VERSION, - Schema.Installed.ROW_VERSION_CODE, Schema.Installed.ROW_SIGNATURE - ), - selection = Pair("${Schema.Installed.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)), - signal = signal - ).use { it.firstOrNull()?.let(::transform) } - } - - // Done in insert - private fun put(installedItem: InstalledItem, notify: Boolean) { - db.insertOrReplace(true, Schema.Installed.name, ContentValues().apply { - put(Schema.Installed.ROW_PACKAGE_NAME, installedItem.packageName) - put(Schema.Installed.ROW_VERSION, installedItem.version) - put(Schema.Installed.ROW_VERSION_CODE, installedItem.versionCode) - put(Schema.Installed.ROW_SIGNATURE, installedItem.signature) - }) - if (notify) { - notifyChanged(Subject.Products) - } - } - - // Done in insert - fun put(installedItem: InstalledItem) = put(installedItem, true) - - // Done in insert - fun putAll(installedItems: List) { - db.beginTransaction() - try { - db.delete(Schema.Installed.name, null, null) - installedItems.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun delete(packageName: String) { - val count = db.delete( - Schema.Installed.name, - "${Schema.Installed.ROW_PACKAGE_NAME} = ?", - arrayOf(packageName) - ) - if (count > 0) { - notifyChanged(Subject.Products) - } - } - - // Unnecessary with Room - private fun transform(cursor: Cursor): InstalledItem { - return InstalledItem( - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_PACKAGE_NAME)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_VERSION)), - cursor.getLong(cursor.getColumnIndex(Schema.Installed.ROW_VERSION_CODE)), - cursor.getString(cursor.getColumnIndex(Schema.Installed.ROW_SIGNATURE)) - ) - } - } - - // Done with some changes in DAOs - object LockAdapter { - // Done in insert (Lock object instead of pair) - private fun put(lock: Pair, notify: Boolean) { - db.insertOrReplace(true, Schema.Lock.name, ContentValues().apply { - put(Schema.Lock.ROW_PACKAGE_NAME, lock.first) - put(Schema.Lock.ROW_VERSION_CODE, lock.second) - }) - if (notify) { - notifyChanged(Subject.Products) - } - } - - // Done in insert (Lock object instead of pair) - fun put(lock: Pair) = put(lock, true) - - // Done in insert (Lock object instead of pair) - fun putAll(locks: List>) { - db.beginTransaction() - try { - db.delete(Schema.Lock.name, null, null) - locks.forEach { put(it, false) } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun delete(packageName: String) { - db.delete(Schema.Lock.name, "${Schema.Lock.ROW_PACKAGE_NAME} = ?", arrayOf(packageName)) - notifyChanged(Subject.Products) - } - } - - // Done - object UpdaterAdapter { - private val Table.temporaryName: String - get() = "${name}_temporary" - - // Done - fun createTemporaryTable() { - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.execSQL(Schema.Product.formatCreateTable(Schema.Product.temporaryName)) - db.execSQL(Schema.Category.formatCreateTable(Schema.Category.temporaryName)) - } - - // Done - fun putTemporary(products: List) { - db.beginTransaction() - try { - for (product in products) { - // Format signatures like ".signature1.signature2." for easier select - val signatures = product.signatures.joinToString { ".$it" } - .let { if (it.isNotEmpty()) "$it." else "" } - db.insertOrReplace(true, Schema.Product.temporaryName, ContentValues().apply { - put(Schema.Product.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Product.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Product.ROW_NAME, product.name) - put(Schema.Product.ROW_SUMMARY, product.summary) - put(Schema.Product.ROW_DESCRIPTION, product.description) - put(Schema.Product.ROW_ADDED, product.added) - put(Schema.Product.ROW_UPDATED, product.updated) - put(Schema.Product.ROW_VERSION_CODE, product.versionCode) - put(Schema.Product.ROW_SIGNATURES, signatures) - put(Schema.Product.ROW_COMPATIBLE, if (product.compatible) 1 else 0) - put(Schema.Product.ROW_DATA, jsonGenerate(product::serialize)) - put(Schema.Product.ROW_DATA_ITEM, jsonGenerate(product.item()::serialize)) - }) - for (category in product.categories) { - db.insertOrReplace( - true, - Schema.Category.temporaryName, - ContentValues().apply { - put(Schema.Category.ROW_REPOSITORY_ID, product.repositoryId) - put(Schema.Category.ROW_PACKAGE_NAME, product.packageName) - put(Schema.Category.ROW_NAME, category) - }) - } - } - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - } - - // Done - fun finishTemporary(repository: Repository, success: Boolean) { - if (success) { - db.beginTransaction() - try { - db.delete( - Schema.Product.name, "${Schema.Product.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString()) - ) - db.delete( - Schema.Category.name, "${Schema.Category.ROW_REPOSITORY_ID} = ?", - arrayOf(repository.id.toString()) - ) - db.execSQL("INSERT INTO ${Schema.Product.name} SELECT * FROM ${Schema.Product.temporaryName}") - db.execSQL("INSERT INTO ${Schema.Category.name} SELECT * FROM ${Schema.Category.temporaryName}") - RepositoryAdapter.putWithoutNotification(repository, true) - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - db.setTransactionSuccessful() - } finally { - db.endTransaction() - } - if (success) { - notifyChanged( - Subject.Repositories, - Subject.Repository(repository.id), - Subject.Products - ) - } - } else { - db.execSQL("DROP TABLE IF EXISTS ${Schema.Product.temporaryName}") - db.execSQL("DROP TABLE IF EXISTS ${Schema.Category.temporaryName}") - } - } - } }