From 7cbcbad40fd31cfe0f783136c4fd0912e7fddb3d Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Wed, 27 Oct 2021 23:22:14 +0200 Subject: [PATCH 01/17] Add: TODOs for (5/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/Database.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index 61aef765..b514bfea 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -34,6 +34,7 @@ object Database { private lateinit var db: SQLiteDatabase + // not needed with Room private interface Table { val memory: Boolean val innerName: String @@ -61,6 +62,7 @@ object Database { } private object Schema { + // implemented object Repository : Table { const val ROW_ID = "_id" const val ROW_ENABLED = "enabled" @@ -77,6 +79,7 @@ object Database { """ } + // implemented object Product : Table { const val ROW_REPOSITORY_ID = "repository_id" const val ROW_PACKAGE_NAME = "package_name" @@ -111,6 +114,7 @@ object Database { override val createIndex = ROW_PACKAGE_NAME } + // implemented object Category : Table { const val ROW_REPOSITORY_ID = "repository_id" const val ROW_PACKAGE_NAME = "package_name" @@ -127,6 +131,7 @@ object Database { override val createIndex = "$ROW_PACKAGE_NAME, $ROW_NAME" } + // implemented object Installed : Table { const val ROW_PACKAGE_NAME = "package_name" const val ROW_VERSION = "version" @@ -143,6 +148,7 @@ object Database { """ } + // implemented object Lock : Table { const val ROW_PACKAGE_NAME = "package_name" const val ROW_VERSION_CODE = "version_code" @@ -155,12 +161,14 @@ object Database { """ } + // 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 private class Helper(context: Context) : SQLiteOpenHelper(context, "droidify", null, 1) { var created = false private set @@ -198,6 +206,7 @@ object Database { } } + // 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( @@ -220,6 +229,7 @@ object Database { } } + // not needed remove after migration private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) { val shouldVacuum = tables.map { val sqls = db.query( @@ -248,6 +258,7 @@ object Database { } } + // TODO not needed, remove after migration private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) { val tables = db.query( "sqlite_master", columns = arrayOf("name"), @@ -274,6 +285,8 @@ object Database { 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) { @@ -290,6 +303,7 @@ object Database { } } + // TODO not needed remove after migration (replaced by LiveData) fun observable(subject: Subject): Observable { return Observable.create { val callback: () -> Unit = { it.onNext(Unit) } @@ -299,12 +313,14 @@ 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, @@ -350,6 +366,7 @@ object Database { return outputStream.toByteArray() } + // Partially done, only object RepositoryAdapter { // Done in put internal fun putWithoutNotification(repository: Repository, shouldReplace: Boolean): Long { @@ -498,7 +515,7 @@ object Database { .use { it.firstOrNull()?.getInt(0) ?: 0 } } - // Complex left to wiring phase + // TODO Too complex left to wiring phase fun query( installed: Boolean, updates: Boolean, searchQuery: String, section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? @@ -596,7 +613,7 @@ object Database { } } - // Unnecessary with Room + // TODO should be taken care of with extra functions that build on Room's queries fun transformItem(cursor: Cursor): ProductItem { return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) .jsonParse { @@ -623,6 +640,7 @@ object Database { } } + // Done object CategoryAdapter { // Done fun getAll(signal: CancellationSignal?): Set { @@ -642,6 +660,7 @@ object Database { } } + // Done object InstalledAdapter { // Done fun get(packageName: String, signal: CancellationSignal?): InstalledItem? { @@ -707,6 +726,7 @@ object Database { } } + // Done with some changes in DAOS object LockAdapter { // Done in insert (Lock object instead of pair) private fun put(lock: Pair, notify: Boolean) { From 1ec70dd1a86916e09d5352824ee29d7cbb3def3e Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Wed, 27 Oct 2021 23:22:45 +0200 Subject: [PATCH 02/17] Update: Abstract part of the DAOs --- .../com/looker/droidify/database/DAOs.kt | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index d332a6a0..5cf589de 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -1,17 +1,20 @@ package com.looker.droidify.database -import android.database.SQLException import androidx.room.* -@Dao -interface RepositoryDao { +interface BaseDao { @Insert - @Throws(SQLException::class) - fun insert(vararg repository: Repository) + fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) - fun update(vararg repository: Repository?) + fun update(vararg obj: T) + @Delete + fun delete(obj: T) +} + +@Dao +interface RepositoryDao : BaseDao { fun put(repository: Repository) { if (repository.id >= 0L) update(repository) else insert(repository) } @@ -25,20 +28,18 @@ interface RepositoryDao { @get:Query("SELECT _id, deleted FROM repository WHERE deleted != 0 and enabled == 0 ORDER BY _id ASC") val allDisabledDeleted: List - @Delete - fun delete(repository: Repository) - @Query("DELETE FROM repository WHERE _id = :id") fun deleteById(vararg id: Long): Int + // TODO optimize @Update(onConflict = OnConflictStrategy.REPLACE) fun markAsDeleted(id: Long) { - update(get(id).apply { this?.deleted = 1 }) + get(id).apply { this?.deleted = 1 }?.let { update(it) } } } @Dao -interface ProductDao { +interface ProductDao : BaseDao { @Query("SELECT COUNT(*) FROM product WHERE repository_id = :id") fun countForRepository(id: Long): Long @@ -50,7 +51,7 @@ interface ProductDao { } @Dao -interface CategoryDao { +interface CategoryDao : BaseDao { @Query( """SELECT DISTINCT category.name FROM category AS category @@ -66,11 +67,7 @@ interface CategoryDao { } @Dao -interface InstalledDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - @Throws(SQLException::class) - fun insert(vararg installed: Installed) - +interface InstalledDao : BaseDao { @Query("SELECT * FROM installed WHERE package_name = :packageName") fun get(packageName: String): Installed? @@ -79,11 +76,7 @@ interface InstalledDao { } @Dao -interface LockDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - @Throws(SQLException::class) - fun insert(vararg lock: Lock) - +interface LockDao : BaseDao { @Query("DELETE FROM lock WHERE package_name = :packageName") fun delete(packageName: String) } \ No newline at end of file From 4b39477b36275a050eeb6a84084300a583e85689 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 00:42:54 +0200 Subject: [PATCH 03/17] Update: Make Common a file --- src/main/kotlin/com/looker/droidify/Common.kt | 17 +++++++------- .../com/looker/droidify/MainApplication.kt | 6 ++--- .../droidify/service/DownloadService.kt | 21 ++++++++--------- .../looker/droidify/service/SyncService.kt | 23 ++++++++----------- 4 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index a943ba42..d520b4d4 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -1,13 +1,12 @@ package com.looker.droidify -object Common { - const val NOTIFICATION_CHANNEL_SYNCING = "syncing" - const val NOTIFICATION_CHANNEL_UPDATES = "updates" - const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading" +const val NOTIFICATION_CHANNEL_SYNCING = "syncing" +const val NOTIFICATION_CHANNEL_UPDATES = "updates" +const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading" - const val NOTIFICATION_ID_SYNCING = 1 - const val NOTIFICATION_ID_UPDATES = 2 - const val NOTIFICATION_ID_DOWNLOADING = 3 +const val NOTIFICATION_ID_SYNCING = 1 +const val NOTIFICATION_ID_UPDATES = 2 +const val NOTIFICATION_ID_DOWNLOADING = 3 + +const val JOB_ID_SYNC = 1 - const val JOB_ID_SYNC = 1 -} diff --git a/src/main/kotlin/com/looker/droidify/MainApplication.kt b/src/main/kotlin/com/looker/droidify/MainApplication.kt index f332473e..f6c10af3 100644 --- a/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -104,19 +104,19 @@ class MainApplication : Application(), ImageLoaderFactory { private fun updateSyncJob(force: Boolean) { val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler - val reschedule = force || !jobScheduler.allPendingJobs.any { it.id == Common.JOB_ID_SYNC } + val reschedule = force || !jobScheduler.allPendingJobs.any { it.id == JOB_ID_SYNC } if (reschedule) { val autoSync = Preferences[Preferences.Key.AutoSync] when (autoSync) { Preferences.AutoSync.Never -> { - jobScheduler.cancel(Common.JOB_ID_SYNC) + jobScheduler.cancel(JOB_ID_SYNC) } Preferences.AutoSync.Wifi, Preferences.AutoSync.Always -> { val period = 12 * 60 * 60 * 1000L // 12 hours val wifiOnly = autoSync == Preferences.AutoSync.Wifi jobScheduler.schedule(JobInfo .Builder( - Common.JOB_ID_SYNC, + JOB_ID_SYNC, ComponentName(this, SyncService.Job::class.java) ) .setRequiredNetworkType(if (wifiOnly) JobInfo.NETWORK_TYPE_UNMETERED else JobInfo.NETWORK_TYPE_ANY) diff --git a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt index f9dd05c0..2c25b39c 100644 --- a/src/main/kotlin/com/looker/droidify/service/DownloadService.kt +++ b/src/main/kotlin/com/looker/droidify/service/DownloadService.kt @@ -9,10 +9,7 @@ import android.content.Intent import android.net.Uri import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat -import com.looker.droidify.BuildConfig -import com.looker.droidify.Common -import com.looker.droidify.MainActivity -import com.looker.droidify.R +import com.looker.droidify.* import com.looker.droidify.content.Cache import com.looker.droidify.content.Preferences import com.looker.droidify.entity.Release @@ -119,7 +116,7 @@ class DownloadService : ConnectionService() { } else { cancelTasks(packageName) cancelCurrentTask(packageName) - notificationManager.cancel(task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING) + notificationManager.cancel(task.notificationTag, NOTIFICATION_ID_DOWNLOADING) tasks += task if (currentTask == null) { handleDownload() @@ -151,7 +148,7 @@ class DownloadService : ConnectionService() { if (Android.sdk(26)) { NotificationChannel( - Common.NOTIFICATION_CHANNEL_DOWNLOADING, + NOTIFICATION_CHANNEL_DOWNLOADING, getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW ) .apply { setShowBadge(false) } @@ -208,9 +205,9 @@ class DownloadService : ConnectionService() { private fun showNotificationError(task: Task, errorType: ErrorType) { notificationManager.notify(task.notificationTag, - Common.NOTIFICATION_ID_DOWNLOADING, + NOTIFICATION_ID_DOWNLOADING, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setAutoCancel(true) .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor( @@ -275,8 +272,8 @@ class DownloadService : ConnectionService() { private fun showNotificationInstall(task: Task) { notificationManager.notify( - task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + task.notificationTag, NOTIFICATION_ID_DOWNLOADING, NotificationCompat + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setAutoCancel(true) .setSmallIcon(android.R.drawable.stat_sys_download_done) .setColor( @@ -373,7 +370,7 @@ class DownloadService : ConnectionService() { private val stateNotificationBuilder by lazy { NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING) + .Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING) .setSmallIcon(android.R.drawable.stat_sys_download) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -395,7 +392,7 @@ class DownloadService : ConnectionService() { private fun publishForegroundState(force: Boolean, state: State) { if (force || currentTask != null) { currentTask = currentTask?.copy(lastState = state) - startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { + startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { when (state) { is State.Connecting -> { setContentTitle(getString(R.string.downloading_FORMAT, state.name)) diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 4d997851..184ea04b 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -13,10 +13,7 @@ import android.text.style.ForegroundColorSpan import android.view.ContextThemeWrapper import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment -import com.looker.droidify.BuildConfig -import com.looker.droidify.Common -import com.looker.droidify.MainActivity -import com.looker.droidify.R +import com.looker.droidify.* import com.looker.droidify.content.Preferences import com.looker.droidify.database.Database import com.looker.droidify.entity.ProductItem @@ -116,7 +113,7 @@ class SyncService : ConnectionService() { fun setUpdateNotificationBlocker(fragment: Fragment?) { updateNotificationBlockerFragment = fragment?.let(::WeakReference) if (fragment != null) { - notificationManager.cancel(Common.NOTIFICATION_ID_UPDATES) + notificationManager.cancel(NOTIFICATION_ID_UPDATES) } } @@ -159,13 +156,13 @@ class SyncService : ConnectionService() { if (Android.sdk(26)) { NotificationChannel( - Common.NOTIFICATION_CHANNEL_SYNCING, + NOTIFICATION_CHANNEL_SYNCING, getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW ) .apply { setShowBadge(false) } .let(notificationManager::createNotificationChannel) NotificationChannel( - Common.NOTIFICATION_CHANNEL_UPDATES, + NOTIFICATION_CHANNEL_UPDATES, getString(R.string.updates), NotificationManager.IMPORTANCE_LOW ) .let(notificationManager::createNotificationChannel) @@ -213,8 +210,8 @@ class SyncService : ConnectionService() { private fun showNotificationError(repository: Repository, exception: Exception) { notificationManager.notify( - "repository-${repository.id}", Common.NOTIFICATION_ID_SYNCING, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING) + "repository-${repository.id}", NOTIFICATION_ID_SYNCING, NotificationCompat + .Builder(this, NOTIFICATION_CHANNEL_SYNCING) .setSmallIcon(android.R.drawable.stat_sys_warning) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -240,7 +237,7 @@ class SyncService : ConnectionService() { private val stateNotificationBuilder by lazy { NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING) + .Builder(this, NOTIFICATION_CHANNEL_SYNCING) .setSmallIcon(R.drawable.ic_sync) .setColor( ContextThemeWrapper(this, R.style.Theme_Main_Light) @@ -263,7 +260,7 @@ class SyncService : ConnectionService() { if (force || currentTask?.lastState != state) { currentTask = currentTask?.copy(lastState = state) if (started == Started.MANUAL) { - startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { + startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply { when (state) { is State.Connecting -> { setContentTitle(getString(R.string.syncing_FORMAT, state.name)) @@ -420,8 +417,8 @@ class SyncService : ConnectionService() { val maxUpdates = 5 fun T.applyHack(callback: T.() -> Unit): T = apply(callback) notificationManager.notify( - Common.NOTIFICATION_ID_UPDATES, NotificationCompat - .Builder(this, Common.NOTIFICATION_CHANNEL_UPDATES) + NOTIFICATION_ID_UPDATES, NotificationCompat + .Builder(this, NOTIFICATION_CHANNEL_UPDATES) .setSmallIcon(R.drawable.ic_new_releases) .setContentTitle(getString(R.string.new_updates_available)) .setContentText( From c99c1be8dd0a058d25de742188b184fceaf651e6 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 01:12:16 +0200 Subject: [PATCH 04/17] Update: Migrate Product.query (4.2/5 in replacing SQLite with Room) --- src/main/kotlin/com/looker/droidify/Common.kt | 25 +++++ .../com/looker/droidify/database/DAOs.kt | 98 +++++++++++++++++++ .../com/looker/droidify/database/Database.kt | 2 +- .../looker/droidify/database/QueryBuilder.kt | 4 +- 4 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index d520b4d4..6dca68af 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -8,5 +8,30 @@ const val NOTIFICATION_ID_SYNCING = 1 const val NOTIFICATION_ID_UPDATES = 2 const val NOTIFICATION_ID_DOWNLOADING = 3 +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" +const val ROW_VERSION = "version" +const val ROW_SIGNATURE = "signature" +const val ROW_ID = "_id" +const val ROW_ENABLED = "enabled" +const val ROW_DELETED = "deleted" +const val ROW_CAN_UPDATE = "can_update" +const val ROW_MATCH_RANK = "match_rank" +const val ROW_REPOSITORY_NAME = "repository" +const val ROW_PRODUCT_NAME = "product" +const val ROW_CATEGORY_NAME = "category" +const val ROW_INSTALLED_NAME = "memory.installed" +const val ROW_LOCK_NAME = "memory.lock" + const val JOB_ID_SYNC = 1 diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 5cf589de..e43a48da 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -1,6 +1,13 @@ package com.looker.droidify.database +import android.database.Cursor +import android.os.CancellationSignal import androidx.room.* +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteQuery +import com.looker.droidify.* +import com.looker.droidify.entity.ProductItem + interface BaseDao { @Insert @@ -48,6 +55,97 @@ interface ProductDao : BaseDao { @Query("DELETE FROM product WHERE repository_id = :id") fun deleteById(vararg id: Long): Int + + @RawQuery + fun query( + query: SupportSQLiteQuery + ): Cursor + + // TODO optimize and simplify + @Transaction + fun query( + installed: Boolean, updates: Boolean, searchQuery: String, + section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? + ): Cursor { + val builder = QueryBuilder() + + val signatureMatches = """installed.${ROW_SIGNATURE} IS NOT NULL AND + product.${ROW_SIGNATURES} LIKE ('%.' || installed.${ROW_SIGNATURE} || '.%') AND + product.${ROW_SIGNATURES} != ''""" + + builder += """SELECT product.rowid AS _id, product.${ROW_REPOSITORY_ID}, + product.${ROW_PACKAGE_NAME}, product.${ROW_NAME}, + product.${ROW_SUMMARY}, installed.${ROW_VERSION}, + (COALESCE(lock.${ROW_VERSION_CODE}, -1) NOT IN (0, product.${ROW_VERSION_CODE}) AND + product.${ROW_COMPATIBLE} != 0 AND product.${ROW_VERSION_CODE} > + COALESCE(installed.${ROW_VERSION_CODE}, 0xffffffff) AND $signatureMatches) + AS ${ROW_CAN_UPDATE}, product.${ROW_COMPATIBLE}, + product.${ROW_DATA_ITEM},""" + + if (searchQuery.isNotEmpty()) { + builder += """(((product.${ROW_NAME} LIKE ? OR + product.${ROW_SUMMARY} LIKE ?) * 7) | + ((product.${ROW_PACKAGE_NAME} LIKE ?) * 3) | + (product.${ROW_DESCRIPTION} LIKE ?)) AS ${ROW_MATCH_RANK},""" + builder %= List(4) { "%$searchQuery%" } + } else { + builder += "0 AS ${ROW_MATCH_RANK}," + } + + builder += """MAX((product.${ROW_COMPATIBLE} AND + (installed.${ROW_SIGNATURE} IS NULL OR $signatureMatches)) || + PRINTF('%016X', product.${ROW_VERSION_CODE})) FROM $ROW_PRODUCT_NAME AS product""" + builder += """JOIN $ROW_REPOSITORY_NAME AS repository + ON product.${ROW_REPOSITORY_ID} = repository.${ROW_ID}""" + builder += """LEFT JOIN $ROW_LOCK_NAME AS lock + ON product.${ROW_PACKAGE_NAME} = lock.${ROW_PACKAGE_NAME}""" + + if (!installed && !updates) { + builder += "LEFT" + } + builder += """JOIN $ROW_INSTALLED_NAME AS installed + ON product.${ROW_PACKAGE_NAME} = installed.${ROW_PACKAGE_NAME}""" + + if (section is ProductItem.Section.Category) { + builder += """JOIN $ROW_CATEGORY_NAME AS category + ON product.${ROW_PACKAGE_NAME} = category.${ROW_PACKAGE_NAME}""" + } + + builder += """WHERE repository.${ROW_ENABLED} != 0 AND + repository.${ROW_DELETED} == 0""" + + if (section is ProductItem.Section.Category) { + builder += "AND category.${ROW_NAME} = ?" + builder %= section.name + } else if (section is ProductItem.Section.Repository) { + builder += "AND product.${ROW_REPOSITORY_ID} = ?" + builder %= section.id.toString() + } + + if (searchQuery.isNotEmpty()) { + builder += """AND $ROW_MATCH_RANK > 0""" + } + + builder += "GROUP BY product.${ROW_PACKAGE_NAME} HAVING 1" + + if (updates) { + builder += "AND $ROW_CAN_UPDATE" + } + builder += "ORDER BY" + + if (searchQuery.isNotEmpty()) { + builder += """$ROW_MATCH_RANK DESC,""" + } + + when (order) { + ProductItem.Order.NAME -> Unit + ProductItem.Order.DATE_ADDED -> builder += "product.${ROW_ADDED} DESC," + ProductItem.Order.LAST_UPDATE -> builder += "product.${ROW_UPDATED} DESC," + }::class + builder += "product.${ROW_NAME} COLLATE LOCALIZED ASC" + + return query(SimpleSQLiteQuery(builder.build())) + } } @Dao diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index b514bfea..e266832c 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -515,7 +515,7 @@ object Database { .use { it.firstOrNull()?.getInt(0) ?: 0 } } - // TODO Too complex left to wiring phase + // Done fun query( installed: Boolean, updates: Boolean, searchQuery: String, section: ProductItem.Section, order: ProductItem.Order, signal: CancellationSignal? diff --git a/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt b/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt index c81618dd..86d3bcb1 100644 --- a/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt +++ b/src/main/kotlin/com/looker/droidify/database/QueryBuilder.kt @@ -33,6 +33,8 @@ class QueryBuilder { this.arguments += arguments } + fun build() = builder.toString() + fun query(db: SQLiteDatabase, signal: CancellationSignal?): Cursor { val query = builder.toString() val arguments = arguments.toTypedArray() @@ -47,4 +49,4 @@ class QueryBuilder { } return db.rawQuery(query, arguments, signal) } -} +} \ No newline at end of file From 78bb72c8e1f85ac785ee84d5e633f651c05449e0 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 01:21:04 +0200 Subject: [PATCH 05/17] Update: Tables' names --- src/main/kotlin/com/looker/droidify/database/DAOs.kt | 6 +++--- .../kotlin/com/looker/droidify/database/Tables.kt | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index e43a48da..d65f1d3c 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -166,15 +166,15 @@ interface CategoryDao : BaseDao { @Dao interface InstalledDao : BaseDao { - @Query("SELECT * FROM installed WHERE package_name = :packageName") + @Query("SELECT * FROM `memory.installed` WHERE package_name = :packageName") fun get(packageName: String): Installed? - @Query("DELETE FROM installed WHERE package_name = :packageName") + @Query("DELETE FROM 'memory.installed' WHERE package_name = :packageName") fun delete(packageName: String) } @Dao interface LockDao : BaseDao { - @Query("DELETE FROM lock WHERE package_name = :packageName") + @Query("DELETE FROM 'memory.lock' WHERE package_name = :packageName") fun delete(packageName: String) } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 930fd59b..c9acdc69 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -30,8 +30,8 @@ class Repository { } } -@Entity(primaryKeys = ["repository_id", "package_name"]) -class Product { +@Entity(tableName = "product", primaryKeys = ["repository_id", "package_name"]) +open class Product { var repository_id: Long = 0 var package_name = "" @@ -51,14 +51,14 @@ class Product { var data_item: ProductItem? = null } -@Entity(primaryKeys = ["repository_id", "package_name", "name"]) -class Category { +@Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"]) +open class Category { var repository_id: Long = 0 var package_name = "" var name = "" } -@Entity +@Entity(tableName = "memory.installed") class Installed { @PrimaryKey var package_name = "" @@ -68,7 +68,7 @@ class Installed { var signature = "" } -@Entity +@Entity(tableName = "memory.lock") class Lock { @PrimaryKey var package_name = "" From a59bd2bd8ad080c3f451dd2cbd999def0e60df03 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Thu, 28 Oct 2021 02:08:07 +0200 Subject: [PATCH 06/17] Update: Migrate transformers (4.3/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/Database.kt | 9 ++--- .../com/looker/droidify/utility/Utils.kt | 36 ++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index e266832c..ffe45792 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -477,7 +477,7 @@ object Database { ).observable(Subject.Repositories) } - // Unnecessary with Room + // Done fun transform(cursor: Cursor): Repository { return cursor.getBlob(cursor.getColumnIndex(Schema.Repository.ROW_DATA)) .jsonParse { @@ -600,7 +600,7 @@ object Database { return builder.query(db, signal).observable(Subject.Products) } - // Unnecessary with Room + // Done private fun transform(cursor: Cursor): Product { return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA)) .jsonParse { @@ -613,7 +613,7 @@ object Database { } } - // TODO should be taken care of with extra functions that build on Room's queries + // Done fun transformItem(cursor: Cursor): ProductItem { return cursor.getBlob(cursor.getColumnIndex(Schema.Product.ROW_DATA_ITEM)) .jsonParse { @@ -726,7 +726,7 @@ object Database { } } - // Done with some changes in DAOS + // Done with some changes in DAOs object LockAdapter { // Done in insert (Lock object instead of pair) private fun put(lock: Pair, notify: Boolean) { @@ -766,6 +766,7 @@ object Database { 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}") diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index 657959f5..280886f1 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -3,11 +3,14 @@ package com.looker.droidify.utility import android.content.Context import android.content.pm.PackageInfo import android.content.pm.Signature +import android.database.Cursor import android.graphics.drawable.Drawable -import com.looker.droidify.R +import com.looker.droidify.* import com.looker.droidify.content.Preferences +import com.looker.droidify.database.Database.jsonParse 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.service.Connection import com.looker.droidify.service.DownloadService @@ -109,3 +112,34 @@ object Utils { } else Unit } } + +fun Cursor.getProduct(): Product = getBlob(getColumnIndex(ROW_DATA)) + .jsonParse { + Product.deserialize(it).apply { + this.repositoryId = getLong(getColumnIndex(ROW_REPOSITORY_ID)) + this.description = getString(getColumnIndex(ROW_DESCRIPTION)) + } + } + + +fun Cursor.getProductItem(): ProductItem = getBlob(getColumnIndex(ROW_DATA_ITEM)) + .jsonParse { + ProductItem.deserialize(it).apply { + this.repositoryId = getLong(getColumnIndex(ROW_REPOSITORY_ID)) + this.packageName = getString(getColumnIndex(ROW_PACKAGE_NAME)) + this.name = getString(getColumnIndex(ROW_NAME)) + this.summary = getString(getColumnIndex(ROW_SUMMARY)) + this.installedVersion = getString(getColumnIndex(ROW_VERSION)) + .orEmpty() + this.compatible = getInt(getColumnIndex(ROW_COMPATIBLE)) != 0 + this.canUpdate = getInt(getColumnIndex(ROW_CAN_UPDATE)) != 0 + this.matchRank = getInt(getColumnIndex(ROW_MATCH_RANK)) + } + } + +fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) + .jsonParse { + Repository.deserialize(it).apply { + this.id = getLong(getColumnIndex(ROW_ID)) + } + } \ No newline at end of file From 6aab888c0fd9b32e775516e60a6725bb8fca52fd Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 29 Oct 2021 23:30:12 +0200 Subject: [PATCH 07/17] Update: Migrate temporary tables (4.5/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/DAOs.kt | 52 +++++++++++++++++++ .../com/looker/droidify/database/Database.kt | 4 +- .../com/looker/droidify/database/DatabaseX.kt | 22 ++++++-- .../com/looker/droidify/database/Tables.kt | 22 +++++--- 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index d65f1d3c..fd689463 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -177,4 +177,56 @@ interface InstalledDao : BaseDao { interface LockDao : BaseDao { @Query("DELETE FROM 'memory.lock' WHERE package_name = :packageName") fun delete(packageName: String) +} + +@Dao +interface ProductTempDao : BaseDao { + @get:Query("SELECT * FROM `product.temporary`") + val all: Array + + @Query("DELETE FROM `product.temporary`") + fun emptyTable() + + @Insert + fun insertCategory(vararg product: CategoryTemp) + + @Transaction + fun putTemporary(products: List) { + products.forEach { + val signatures = it.signatures.joinToString { ".$it" } + .let { if (it.isNotEmpty()) "$it." else "" } + insert(it.let { + ProductTemp().apply { + repository_id = it.repositoryId + package_name = it.packageName + name = it.name + summary = it.summary + description = it.description + added = it.added + updated = it.updated + version_code = it.versionCode + this.signatures = signatures + compatible = if (it.compatible) 1 else 0 + data = it + data_item = it.item() + } + }) + it.categories.forEach { category -> + insertCategory(CategoryTemp().apply { + repository_id = it.repositoryId + package_name = it.packageName + name = category + }) + } + } + } +} + +@Dao +interface CategoryTempDao : BaseDao { + @get:Query("SELECT * FROM `category.temporary`") + val all: Array + + @Query("DELETE FROM `category.temporary`") + fun emptyTable() } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Database.kt b/src/main/kotlin/com/looker/droidify/database/Database.kt index ffe45792..271181db 100644 --- a/src/main/kotlin/com/looker/droidify/database/Database.kt +++ b/src/main/kotlin/com/looker/droidify/database/Database.kt @@ -761,7 +761,7 @@ object Database { } } - // TODO add temporary tables + // Done object UpdaterAdapter { private val Table.temporaryName: String get() = "${name}_temporary" @@ -774,6 +774,7 @@ object Database { db.execSQL(Schema.Category.formatCreateTable(Schema.Category.temporaryName)) } + // Done fun putTemporary(products: List) { db.beginTransaction() try { @@ -812,6 +813,7 @@ object Database { } } + // Done fun finishTemporary(repository: Repository, success: Boolean) { if (success) { db.beginTransaction() diff --git a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt index 2a8e02ea..bff2a514 100644 --- a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt +++ b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt @@ -1,16 +1,16 @@ package com.looker.droidify.database import android.content.Context +import androidx.room.* import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.TypeConverters @Database( entities = [ Repository::class, Product::class, + ProductTemp::class, Category::class, + CategoryTemp::class, Installed::class, Lock::class ], version = 1 @@ -19,7 +19,9 @@ import androidx.room.TypeConverters abstract class DatabaseX : RoomDatabase() { abstract val repositoryDao: RepositoryDao abstract val productDao: ProductDao + abstract val productTempDao: ProductTempDao abstract val categoryDao: CategoryDao + abstract val categoryTempDao: CategoryTempDao abstract val installedDao: InstalledDao abstract val lockDao: LockDao @@ -45,6 +47,7 @@ abstract class DatabaseX : RoomDatabase() { } } + @Transaction fun cleanUp(pairs: Set>) { val result = pairs.windowed(10, 10, true).map { val ids = it.map { it.first }.toLongArray() @@ -59,4 +62,17 @@ abstract class DatabaseX : RoomDatabase() { com.looker.droidify.database.Database.notifyChanged(com.looker.droidify.database.Database.Subject.Products) }*/ } + + @Transaction + fun finishTemporary(repository: com.looker.droidify.entity.Repository, success: Boolean) { + if (success) { + productDao.deleteById(repository.id) + categoryDao.deleteById(repository.id) + productDao.insert(*(productTempDao.all)) + categoryDao.insert(*(categoryTempDao.all)) + repositoryDao.put(repository) + } + productTempDao.emptyTable() + categoryTempDao.emptyTable() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index c9acdc69..626550c8 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -6,7 +6,6 @@ import androidx.room.PrimaryKey import androidx.room.TypeConverter import com.looker.droidify.database.Database.jsonGenerate import com.looker.droidify.database.Database.jsonParse -import com.looker.droidify.entity.Product import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository @@ -32,25 +31,28 @@ class Repository { @Entity(tableName = "product", primaryKeys = ["repository_id", "package_name"]) open class Product { - var repository_id: Long = 0 + var repository_id = 0L var package_name = "" var name = "" var summary = "" var description = "" - var added = 0 - var updated = 0 - var version_code = 0 + var added = 0L + var updated = 0L + var version_code = 0L var signatures = "" var compatible = 0 @ColumnInfo(typeAffinity = ColumnInfo.BLOB) - var data: Product? = null + var data: com.looker.droidify.entity.Product? = null @ColumnInfo(typeAffinity = ColumnInfo.BLOB) var data_item: ProductItem? = null } +@Entity(tableName = "product.temporary") +class ProductTemp : Product() + @Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"]) open class Category { var repository_id: Long = 0 @@ -58,6 +60,9 @@ open class Category { var name = "" } +@Entity(tableName = "category.temporary") +class CategoryTemp : Category() + @Entity(tableName = "memory.installed") class Installed { @PrimaryKey @@ -87,11 +92,12 @@ object Converters { @TypeConverter @JvmStatic - fun toProduct(byteArray: ByteArray) = byteArray.jsonParse { Product.deserialize(it) } + fun toProduct(byteArray: ByteArray) = + byteArray.jsonParse { com.looker.droidify.entity.Product.deserialize(it) } @TypeConverter @JvmStatic - fun toByteArray(product: Product) = jsonGenerate(product::serialize) + fun toByteArray(product: com.looker.droidify.entity.Product) = jsonGenerate(product::serialize) @TypeConverter @JvmStatic From 73caf07ba1bac092fd2b9e3bfa95e8cbad5313bb Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Fri, 29 Oct 2021 23:33:58 +0200 Subject: [PATCH 08/17] Fix: Put of repositoryDao --- src/main/kotlin/com/looker/droidify/database/DAOs.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index fd689463..235666e8 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -22,8 +22,16 @@ interface BaseDao { @Dao interface RepositoryDao : BaseDao { - fun put(repository: Repository) { - if (repository.id >= 0L) update(repository) else insert(repository) + fun put(repository: com.looker.droidify.entity.Repository) { + repository.let { + val dbRepo = Repository().apply { + id = it.id + enabled = if (it.enabled) 1 else 0 + deleted = 0 + data = it + } + if (repository.id >= 0L) update(dbRepo) else insert(dbRepo) + } } @Query("SELECT * FROM repository WHERE _id = :id and deleted == 0") From acf3f0d1fd041a63adc7b36b95c885c5f62a9f3c Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 00:58:35 +0100 Subject: [PATCH 09/17] Update: Migrate Json utils --- .../com/looker/droidify/database/Tables.kt | 4 ++-- .../com/looker/droidify/utility/Utils.kt | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 626550c8..18f6e3bf 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -4,10 +4,10 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.TypeConverter -import com.looker.droidify.database.Database.jsonGenerate -import com.looker.droidify.database.Database.jsonParse import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository +import com.looker.droidify.utility.jsonGenerate +import com.looker.droidify.utility.jsonParse @Entity class Repository { diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index 280886f1..b5bb6c88 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -5,9 +5,10 @@ import android.content.pm.PackageInfo import android.content.pm.Signature import android.database.Cursor import android.graphics.drawable.Drawable +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser import com.looker.droidify.* import com.looker.droidify.content.Preferences -import com.looker.droidify.database.Database.jsonParse import com.looker.droidify.entity.InstalledItem import com.looker.droidify.entity.Product import com.looker.droidify.entity.ProductItem @@ -17,9 +18,13 @@ import com.looker.droidify.service.DownloadService import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.singleSignature import com.looker.droidify.utility.extension.android.versionCodeCompat +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 com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.extension.resources.getDrawableCompat import com.looker.droidify.utility.extension.text.hex +import java.io.ByteArrayOutputStream import java.security.MessageDigest import java.security.cert.Certificate import java.security.cert.CertificateEncodingException @@ -142,4 +147,14 @@ fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) Repository.deserialize(it).apply { this.id = getLong(getColumnIndex(ROW_ID)) } - } \ No newline at end of file + } + +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() +} \ No newline at end of file From d875fc57c90a1342529ae202a7db75e279d0404f Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:00:16 +0100 Subject: [PATCH 10/17] Update: Migrate transform to InstalledItem method --- src/main/kotlin/com/looker/droidify/utility/Utils.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/kotlin/com/looker/droidify/utility/Utils.kt b/src/main/kotlin/com/looker/droidify/utility/Utils.kt index b5bb6c88..0027d995 100644 --- a/src/main/kotlin/com/looker/droidify/utility/Utils.kt +++ b/src/main/kotlin/com/looker/droidify/utility/Utils.kt @@ -149,6 +149,13 @@ fun Cursor.getRepository(): Repository = getBlob(getColumnIndex(ROW_DATA)) } } +fun Cursor.getInstalledItem(): InstalledItem = InstalledItem( + getString(getColumnIndex(ROW_PACKAGE_NAME)), + getString(getColumnIndex(ROW_VERSION)), + getLong(getColumnIndex(ROW_VERSION_CODE)), + getString(getColumnIndex(ROW_SIGNATURE)) +) + fun ByteArray.jsonParse(callback: (JsonParser) -> T): T { return Json.factory.createParser(this).use { it.parseDictionary(callback) } } From a07c017e21c32390cb69ef818e1a857baf9e45c8 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:00:52 +0100 Subject: [PATCH 11/17] Add: Room schema directory --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 37f34db0..92521a8e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,13 @@ android { targetSdk = 31 versionCode = 35 versionName = "0.3.5" + + javaCompileOptions { + annotationProcessorOptions { + arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] + arguments += ["room.incremental": "true"] + } + } } sourceSets.all { From dedbaefef5e54c1ea6c44095e35fb4ed8505f677 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:01:39 +0100 Subject: [PATCH 12/17] Add: DatabaseX instance to ScreenActivity --- src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt index 518bb288..6e04d3ea 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenActivity.kt @@ -10,6 +10,7 @@ import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.google.android.material.circularreveal.CircularRevealFrameLayout +import com.looker.droidify.MainApplication import com.looker.droidify.R import com.looker.droidify.content.Preferences import com.looker.droidify.database.CursorOwner @@ -25,6 +26,9 @@ abstract class ScreenActivity : AppCompatActivity() { private const val STATE_FRAGMENT_STACK = "fragmentStack" } + val db + get() = (application as MainApplication).db + sealed class SpecialIntent { object Updates : SpecialIntent() class Install(val packageName: String?, val cacheFileName: String?) : SpecialIntent() From 9235ed6e0d19b221e40f382598cc3c8fe756ba2e Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:02:46 +0100 Subject: [PATCH 13/17] Update: Complete the DAOs --- .../com/looker/droidify/database/DAOs.kt | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 235666e8..91c05098 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -14,7 +14,7 @@ interface BaseDao { fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) - fun update(vararg obj: T) + fun update(vararg obj: T): Int @Delete fun delete(obj: T) @@ -22,21 +22,28 @@ interface BaseDao { @Dao interface RepositoryDao : BaseDao { - fun put(repository: com.looker.droidify.entity.Repository) { + fun put(repository: com.looker.droidify.entity.Repository): com.looker.droidify.entity.Repository { repository.let { val dbRepo = Repository().apply { id = it.id enabled = if (it.enabled) 1 else 0 - deleted = 0 + deleted = false data = it } - if (repository.id >= 0L) update(dbRepo) else insert(dbRepo) + val newId = if (repository.id >= 0L) update(dbRepo).toLong() else returnInsert(dbRepo) + return if (newId != repository.id) repository.copy(id = newId) else repository } } + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun returnInsert(product: Repository): Long + @Query("SELECT * FROM repository WHERE _id = :id and deleted == 0") fun get(id: Long): Repository? + @get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC") + val allCursor: Cursor + @get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC") val all: List @@ -49,7 +56,7 @@ interface RepositoryDao : BaseDao { // TODO optimize @Update(onConflict = OnConflictStrategy.REPLACE) fun markAsDeleted(id: Long) { - get(id).apply { this?.deleted = 1 }?.let { update(it) } + get(id).apply { this?.deleted = true }?.let { update(it) } } } @@ -59,7 +66,7 @@ interface ProductDao : BaseDao { fun countForRepository(id: Long): Long @Query("SELECT * FROM product WHERE package_name = :packageName") - fun get(packageName: String): Product? + fun get(packageName: String): List @Query("DELETE FROM product WHERE repository_id = :id") fun deleteById(vararg id: Long): Int @@ -158,7 +165,7 @@ interface ProductDao : BaseDao { @Dao interface CategoryDao : BaseDao { - @Query( + @get:Query( """SELECT DISTINCT category.name FROM category AS category JOIN repository AS repository @@ -166,7 +173,7 @@ interface CategoryDao : BaseDao { WHERE repository.enabled != 0 AND repository.deleted == 0""" ) - fun getAll(): List + val allNames: List @Query("DELETE FROM category WHERE repository_id = :id") fun deleteById(vararg id: Long): Int @@ -174,8 +181,18 @@ interface CategoryDao : BaseDao { @Dao interface InstalledDao : BaseDao { + fun put(vararg isntalled: com.looker.droidify.entity.InstalledItem) { + isntalled.forEach { + insert(Installed(it.packageName).apply { + version = it.version + version_code = it.versionCode + signature = it.signature + }) + } + } + @Query("SELECT * FROM `memory.installed` WHERE package_name = :packageName") - fun get(packageName: String): Installed? + fun get(packageName: String): Cursor @Query("DELETE FROM 'memory.installed' WHERE package_name = :packageName") fun delete(packageName: String) From dc65c060e754838619e6fd86cf0d8fe7a8e41945 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:05:15 +0100 Subject: [PATCH 14/17] Update: Migrate from Database to DatabaseX (4.7/5 in replacing SQLite with Room) --- .../com/looker/droidify/MainApplication.kt | 22 +++++----- .../droidify/content/ProductPreferences.kt | 32 +++++++++------ .../looker/droidify/database/CursorOwner.kt | 9 +++-- .../com/looker/droidify/database/DatabaseX.kt | 40 ++++++++++--------- .../com/looker/droidify/database/Tables.kt | 12 +++--- .../droidify/index/RepositoryUpdater.kt | 32 ++++++++------- .../droidify/screen/EditRepositoryFragment.kt | 10 ++--- .../looker/droidify/screen/ProductFragment.kt | 13 ++++-- .../looker/droidify/screen/ProductsAdapter.kt | 4 +- .../droidify/screen/ProductsFragment.kt | 4 +- .../droidify/screen/RepositoriesAdapter.kt | 4 +- .../droidify/screen/RepositoryFragment.kt | 5 +-- .../droidify/screen/ScreenshotsFragment.kt | 12 ++++-- .../looker/droidify/screen/TabsFragment.kt | 8 ++-- .../looker/droidify/service/SyncService.kt | 20 +++++----- 15 files changed, 128 insertions(+), 99 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/MainApplication.kt b/src/main/kotlin/com/looker/droidify/MainApplication.kt index f6c10af3..52bf688f 100644 --- a/src/main/kotlin/com/looker/droidify/MainApplication.kt +++ b/src/main/kotlin/com/looker/droidify/MainApplication.kt @@ -10,7 +10,7 @@ import coil.ImageLoaderFactory import com.looker.droidify.content.Cache import com.looker.droidify.content.Preferences import com.looker.droidify.content.ProductPreferences -import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX import com.looker.droidify.index.RepositoryUpdater import com.looker.droidify.network.CoilDownloader import com.looker.droidify.network.Downloader @@ -24,19 +24,21 @@ import java.net.Proxy @Suppress("unused") class MainApplication : Application(), ImageLoaderFactory { + lateinit var db: DatabaseX + override fun onCreate() { super.onCreate() - val databaseUpdated = Database.init(this) + db = DatabaseX.getInstance(applicationContext) Preferences.init(this) ProductPreferences.init(this) - RepositoryUpdater.init() + RepositoryUpdater.init(this) listenApplications() listenPreferences() - if (databaseUpdated) { + /*if (databaseUpdated) { forceSyncAll() - } + }*/ Cache.cleanup(this) updateSyncJob(false) @@ -60,9 +62,9 @@ class MainApplication : Application(), ImageLoaderFactory { null } if (packageInfo != null) { - Database.InstalledAdapter.put(packageInfo.toInstalledItem()) + db.installedDao.put(packageInfo.toInstalledItem()) } else { - Database.InstalledAdapter.delete(packageName) + db.installedDao.delete(packageName) } } } @@ -76,7 +78,7 @@ class MainApplication : Application(), ImageLoaderFactory { val installedItems = packageManager.getInstalledPackages(Android.PackageManager.signaturesFlag) .map { it.toInstalledItem() } - Database.InstalledAdapter.putAll(installedItems) + db.installedDao.put(*installedItems.toTypedArray()) } private fun listenPreferences() { @@ -160,9 +162,9 @@ class MainApplication : Application(), ImageLoaderFactory { } private fun forceSyncAll() { - Database.RepositoryAdapter.getAll(null).forEach { + db.repositoryDao.all.mapNotNull { it.data }.forEach { if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) { - Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = "")) + db.repositoryDao.put(it.copy(lastModified = "", entityTag = "")) } } Connection(SyncService::class.java, onBind = { connection, binder -> diff --git a/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt b/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt index fb6ef278..ee3ac97e 100644 --- a/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt +++ b/src/main/kotlin/com/looker/droidify/content/ProductPreferences.kt @@ -2,7 +2,8 @@ package com.looker.droidify.content import android.content.Context import android.content.SharedPreferences -import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX +import com.looker.droidify.database.Lock import com.looker.droidify.entity.ProductPreference import com.looker.droidify.utility.extension.json.Json import com.looker.droidify.utility.extension.json.parseDictionary @@ -16,25 +17,32 @@ object ProductPreferences { private val defaultProductPreference = ProductPreference(false, 0L) private lateinit var preferences: SharedPreferences private val subject = PublishSubject.create>() + lateinit var db: DatabaseX fun init(context: Context) { + db = DatabaseX.getInstance(context) preferences = context.getSharedPreferences("product_preferences", Context.MODE_PRIVATE) - Database.LockAdapter.putAll(preferences.all.keys - .mapNotNull { packageName -> - this[packageName].databaseVersionCode?.let { - Pair( - packageName, - it - ) + db.lockDao.insert(*preferences.all.keys + .mapNotNull { pName -> + this[pName].databaseVersionCode?.let { + Lock().apply { + package_name = pName + version_code = it + } } - }) + } + .toTypedArray() + ) subject .observeOn(Schedulers.io()) - .subscribe { (packageName, versionCode) -> + .subscribe { (pName, versionCode) -> if (versionCode != null) { - Database.LockAdapter.put(Pair(packageName, versionCode)) + db.lockDao.insert(Lock().apply { + package_name = pName + version_code = versionCode + }) } else { - Database.LockAdapter.delete(packageName) + db.lockDao.delete(pName) } } } diff --git a/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt b/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt index b1e47842..10f07d6a 100644 --- a/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt +++ b/src/main/kotlin/com/looker/droidify/database/CursorOwner.kt @@ -87,9 +87,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { override fun onCreateLoader(id: Int, args: Bundle?): Loader { val request = activeRequests[id]!!.request + val db = DatabaseX.getInstance(requireContext()) return QueryLoader(requireContext()) { when (request) { - is Request.ProductsAvailable -> Database.ProductAdapter + is Request.ProductsAvailable -> db.productDao .query( installed = false, updates = false, @@ -98,7 +99,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { order = request.order, signal = it ) - is Request.ProductsInstalled -> Database.ProductAdapter + is Request.ProductsInstalled -> db.productDao .query( installed = true, updates = false, @@ -107,7 +108,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { order = request.order, signal = it ) - is Request.ProductsUpdates -> Database.ProductAdapter + is Request.ProductsUpdates -> db.productDao .query( installed = true, updates = true, @@ -116,7 +117,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks { order = request.order, signal = it ) - is Request.Repositories -> Database.RepositoryAdapter.query(it) + is Request.Repositories -> db.repositoryDao.allCursor } } } diff --git a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt index bff2a514..72a10b27 100644 --- a/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt +++ b/src/main/kotlin/com/looker/droidify/database/DatabaseX.kt @@ -1,8 +1,10 @@ package com.looker.droidify.database import android.content.Context -import androidx.room.* import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters @Database( entities = [ @@ -47,15 +49,16 @@ abstract class DatabaseX : RoomDatabase() { } } - @Transaction fun cleanUp(pairs: Set>) { - val result = pairs.windowed(10, 10, true).map { - val ids = it.map { it.first }.toLongArray() - val productsCount = productDao.deleteById(*ids) - val categoriesCount = categoryDao.deleteById(*ids) - val deleteIds = it.filter { it.second }.map { it.first }.toLongArray() - repositoryDao.deleteById(*deleteIds) - productsCount != 0 || categoriesCount != 0 + runInTransaction { + val result = pairs.windowed(10, 10, true).map { + val ids = it.map { it.first }.toLongArray() + val productsCount = productDao.deleteById(*ids) + val categoriesCount = categoryDao.deleteById(*ids) + val deleteIds = it.filter { it.second }.map { it.first }.toLongArray() + repositoryDao.deleteById(*deleteIds) + productsCount != 0 || categoriesCount != 0 + } } // Use live objects and observers instead /*if (result.any { it }) { @@ -63,16 +66,17 @@ abstract class DatabaseX : RoomDatabase() { }*/ } - @Transaction fun finishTemporary(repository: com.looker.droidify.entity.Repository, success: Boolean) { - if (success) { - productDao.deleteById(repository.id) - categoryDao.deleteById(repository.id) - productDao.insert(*(productTempDao.all)) - categoryDao.insert(*(categoryTempDao.all)) - repositoryDao.put(repository) + runInTransaction { + if (success) { + productDao.deleteById(repository.id) + categoryDao.deleteById(repository.id) + productDao.insert(*(productTempDao.all)) + categoryDao.insert(*(categoryTempDao.all)) + repositoryDao.put(repository) + } + productTempDao.emptyTable() + categoryTempDao.emptyTable() } - productTempDao.emptyTable() - categoryTempDao.emptyTable() } } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 18f6e3bf..3b881cdc 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -16,7 +16,7 @@ class Repository { var id: Long = 0 var enabled = 0 - var deleted = 0 + var deleted = false @ColumnInfo(typeAffinity = ColumnInfo.BLOB) var data: Repository? = null @@ -25,7 +25,7 @@ class Repository { @ColumnInfo(name = "_id") var id = 0L - var deleted = 0 + var deleted = false } } @@ -64,12 +64,12 @@ open class Category { class CategoryTemp : Category() @Entity(tableName = "memory.installed") -class Installed { +class Installed(pName: String = "") { @PrimaryKey - var package_name = "" + var package_name = pName var version = "" - var version_code = 0 + var version_code = 0L var signature = "" } @@ -78,7 +78,7 @@ class Lock { @PrimaryKey var package_name = "" - var version_code = 0 + var version_code = 0L } object Converters { diff --git a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt index 20e63893..1f0b9aee 100644 --- a/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt +++ b/src/main/kotlin/com/looker/droidify/index/RepositoryUpdater.kt @@ -4,6 +4,7 @@ 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 import com.looker.droidify.entity.Repository @@ -59,29 +60,28 @@ object RepositoryUpdater { private val updaterLock = Any() private val cleanupLock = Any() + lateinit var db: DatabaseX - fun init() { - + fun init(context: Context) { + db = DatabaseX.getInstance(context) var lastDisabled = setOf() Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) .flatMapSingle { RxUtils.querySingle { - Database.RepositoryAdapter.getAllDisabledDeleted( - it - ) + db.repositoryDao.allDisabledDeleted } } .forEach { it -> - val newDisabled = it.asSequence().filter { !it.second }.map { it.first }.toSet() + val newDisabled = it.asSequence().filter { !it.deleted }.map { it.id }.toSet() val disabled = newDisabled - lastDisabled lastDisabled = newDisabled - val deleted = it.asSequence().filter { it.second }.map { it.first }.toSet() + val deleted = it.asSequence().filter { it.deleted }.map { it.id }.toSet() if (disabled.isNotEmpty() || deleted.isNotEmpty()) { val pairs = (disabled.asSequence().map { Pair(it, false) } + deleted.asSequence().map { Pair(it, true) }).toSet() - synchronized(cleanupLock) { Database.RepositoryAdapter.cleanup(pairs) } + synchronized(cleanupLock) { db.cleanUp(pairs) } } } } @@ -193,12 +193,14 @@ object RepositoryUpdater { file: File, lastModified: String, entityTag: String, callback: (Stage, Long, Long?) -> Unit ): Boolean { var rollback = true + val db = DatabaseX.getInstance(context) return synchronized(updaterLock) { try { val jarFile = JarFile(file, true) val indexEntry = jarFile.getEntry(indexType.contentName) as JarEntry val total = indexEntry.size - Database.UpdaterAdapter.createTemporaryTable() + db.productTempDao.emptyTable() + db.categoryTempDao.emptyTable() val features = context.packageManager.systemAvailableFeatures .asSequence().map { it.name }.toSet() + setOf("android.hardware.touchscreen") @@ -231,7 +233,7 @@ object RepositoryUpdater { } products += transformProduct(product, features, unstable) if (products.size >= 50) { - Database.UpdaterAdapter.putTemporary(products) + db.productTempDao.putTemporary(products) products.clear() } } @@ -249,7 +251,7 @@ object RepositoryUpdater { throw InterruptedException() } if (products.isNotEmpty()) { - Database.UpdaterAdapter.putTemporary(products) + db.productTempDao.putTemporary(products) products.clear() } Pair(changedRepository, certificateFromIndex) @@ -334,7 +336,7 @@ object RepositoryUpdater { progress.toLong(), totalCount.toLong() ) - Database.UpdaterAdapter.putTemporary(products + db.productTempDao.putTemporary(products .map { transformProduct(it, features, unstable) }) } } @@ -407,7 +409,7 @@ object RepositoryUpdater { } callback(Stage.COMMIT, 0, null) synchronized(cleanupLock) { - Database.UpdaterAdapter.finishTemporary( + db.finishTemporary( commitRepository, true ) @@ -423,7 +425,7 @@ object RepositoryUpdater { } finally { file.delete() if (rollback) { - Database.UpdaterAdapter.finishTemporary(repository, false) + db.finishTemporary(repository, false) } } } diff --git a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt index 99bec4f3..ae5376f4 100644 --- a/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/EditRepositoryFragment.kt @@ -161,7 +161,7 @@ class EditRepositoryFragment() : ScreenFragment() { }) if (savedInstanceState == null) { - val repository = repositoryId?.let(Database.RepositoryAdapter::get) + val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data } if (repository == null) { val clipboardManager = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -240,9 +240,9 @@ class EditRepositoryFragment() : ScreenFragment() { } repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { it -> takenAddresses = it.asSequence().filter { it.id != repositoryId } @@ -462,10 +462,10 @@ class EditRepositoryFragment() : ScreenFragment() { MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager) invalidateState() } else { - val repository = repositoryId?.let(Database.RepositoryAdapter::get) + val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data } ?.edit(address, fingerprint, authentication) ?: Repository.newRepository(address, fingerprint, authentication) - val changedRepository = Database.RepositoryAdapter.put(repository) + val changedRepository = screenActivity.db.repositoryDao.put(repository) if (repositoryId == null && changedRepository.enabled) { binder.sync(changedRepository) } diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt index 97cadc8e..40dcec0e 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductFragment.kt @@ -28,6 +28,7 @@ import com.looker.droidify.utility.Utils import com.looker.droidify.utility.Utils.startUpdate import com.looker.droidify.utility.extension.android.* import com.looker.droidify.utility.extension.text.trimAfter +import com.looker.droidify.utility.getInstalledItem import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -132,12 +133,16 @@ class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks { var first = true productDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) + .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } } + .flatMapSingle { + RxUtils.querySingle { + screenActivity.db.productDao.get(packageName).mapNotNull { it?.data } + } + } .flatMapSingle { products -> RxUtils - .querySingle { Database.RepositoryAdapter.getAll(it) } + .querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } .map { it -> it.asSequence().map { Pair(it.id, it) }.toMap() .let { @@ -154,7 +159,7 @@ class ProductFragment() : ScreenFragment(), ProductAdapter.Callbacks { } .flatMapSingle { products -> RxUtils - .querySingle { Nullable(Database.InstalledAdapter.get(packageName, it)) } + .querySingle { Nullable(screenActivity.db.installedDao.get(packageName).getInstalledItem()) } .map { Pair(products, it) } } .observeOn(AndroidSchedulers.mainThread()) diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt b/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt index 84fccbbd..8041f2b8 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductsAdapter.kt @@ -16,13 +16,13 @@ import com.google.android.material.progressindicator.CircularProgressIndicator 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.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.network.CoilDownloader import com.looker.droidify.utility.Utils import com.looker.droidify.utility.extension.resources.* import com.looker.droidify.utility.extension.text.nullIfEmpty +import com.looker.droidify.utility.getProductItem import com.looker.droidify.widget.CursorRecyclerAdapter class ProductsAdapter(private val onClick: (ProductItem) -> Unit) : @@ -113,7 +113,7 @@ class ProductsAdapter(private val onClick: (ProductItem) -> Unit) : } private fun getProductItem(position: Int): ProductItem { - return Database.ProductAdapter.transformItem(moveTo(position)) + return moveTo(position).getProductItem() } override fun onCreateViewHolder( diff --git a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt index 70a5dc63..9d5ca363 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ProductsFragment.kt @@ -108,9 +108,9 @@ class ProductsFragment() : BaseFragment(), CursorOwner.Callback { screenActivity.cursorOwner.attach(this, request) repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .map { it.asSequence().map { Pair(it.id, it) }.toMap() } .observeOn(AndroidSchedulers.mainThread()) .subscribe { (recyclerView?.adapter as? ProductsAdapter)?.repositories = it } diff --git a/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt b/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt index 245b6f58..aad14873 100644 --- a/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt +++ b/src/main/kotlin/com/looker/droidify/screen/RepositoriesAdapter.kt @@ -6,9 +6,9 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textview.MaterialTextView import com.looker.droidify.R -import com.looker.droidify.database.Database import com.looker.droidify.entity.Repository import com.looker.droidify.utility.extension.resources.inflate +import com.looker.droidify.utility.getRepository import com.looker.droidify.widget.CursorRecyclerAdapter class RepositoriesAdapter( @@ -33,7 +33,7 @@ class RepositoriesAdapter( } private fun getRepository(position: Int): Repository { - return Database.RepositoryAdapter.transform(moveTo(position)) + return moveTo(position).getRepository() } override fun onCreateViewHolder( diff --git a/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt b/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt index 3238025e..c038a0d8 100644 --- a/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/RepositoryFragment.kt @@ -13,7 +13,6 @@ import androidx.core.widget.NestedScrollView import androidx.lifecycle.lifecycleScope import com.google.android.material.textview.MaterialTextView import com.looker.droidify.R -import com.looker.droidify.database.Database import com.looker.droidify.databinding.TitleTextItemBinding import com.looker.droidify.service.Connection import com.looker.droidify.service.SyncService @@ -104,7 +103,7 @@ class RepositoryFragment() : ScreenFragment() { } private fun updateRepositoryView() { - val repository = Database.RepositoryAdapter.get(repositoryId) + val repository = screenActivity.db.repositoryDao.get(repositoryId)?.data val layout = layout!! layout.removeAllViews() if (repository == null) { @@ -130,7 +129,7 @@ class RepositoryFragment() : ScreenFragment() { if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) { layout.addTitleText( R.string.number_of_applications, - Database.ProductAdapter.getCount(repository.id).toString() + screenActivity.db.productDao.countForRepository(repository.id).toString() ) } } else { diff --git a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt index b3d31eab..e157e55c 100644 --- a/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/ScreenshotsFragment.kt @@ -20,6 +20,7 @@ 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 import com.looker.droidify.graphics.PaddingDrawable @@ -68,6 +69,7 @@ class ScreenshotsFragment() : DialogFragment() { val window = dialog.window val decorView = window?.decorView + val db = DatabaseX.getInstance(requireContext()) if (window != null) { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -132,13 +134,17 @@ class ScreenshotsFragment() : DialogFragment() { var restored = false productDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) + .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } } + .flatMapSingle { + RxUtils.querySingle { + db.productDao.get(packageName).mapNotNull { it?.data } + } + } .map { it -> Pair( it.find { it.repositoryId == repositoryId }, - Database.RepositoryAdapter.get(repositoryId) + db.repositoryDao.get(repositoryId)?.data ) } .observeOn(AndroidSchedulers.mainThread()) diff --git a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt index 2612ce84..101abdc7 100644 --- a/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt +++ b/src/main/kotlin/com/looker/droidify/screen/TabsFragment.kt @@ -227,9 +227,9 @@ class TabsFragment : ScreenFragment() { } categoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Products)) + .concatWith(Database.observable(Database.Subject.Products)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.CategoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.categoryDao.allNames } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { setSectionsAndUpdate( @@ -238,9 +238,9 @@ class TabsFragment : ScreenFragment() { ) } repositoriesDisposable = Observable.just(Unit) - .concatWith(Database.observable(Database.Subject.Repositories)) + .concatWith(Database.observable(Database.Subject.Repositories)) // TODO have to be replaced like whole rxJava .observeOn(Schedulers.io()) - .flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } } + .flatMapSingle { RxUtils.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } } .observeOn(AndroidSchedulers.mainThread()) .subscribe { it -> setSectionsAndUpdate(null, it.asSequence().filter { it.enabled } diff --git a/src/main/kotlin/com/looker/droidify/service/SyncService.kt b/src/main/kotlin/com/looker/droidify/service/SyncService.kt index 184ea04b..366f2d60 100644 --- a/src/main/kotlin/com/looker/droidify/service/SyncService.kt +++ b/src/main/kotlin/com/looker/droidify/service/SyncService.kt @@ -15,7 +15,7 @@ import androidx.core.app.NotificationCompat import androidx.fragment.app.Fragment import com.looker.droidify.* import com.looker.droidify.content.Preferences -import com.looker.droidify.database.Database +import com.looker.droidify.database.DatabaseX import com.looker.droidify.entity.ProductItem import com.looker.droidify.entity.Repository import com.looker.droidify.index.RepositoryUpdater @@ -25,6 +25,7 @@ import com.looker.droidify.utility.extension.android.asSequence import com.looker.droidify.utility.extension.android.notificationManager import com.looker.droidify.utility.extension.resources.getColorFromAttr import com.looker.droidify.utility.extension.text.formatSize +import com.looker.droidify.utility.getProductItem import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.Disposable @@ -92,7 +93,7 @@ class SyncService : ConnectionService() { } fun sync(request: SyncRequest) { - val ids = Database.RepositoryAdapter.getAll(null) + val ids = db.repositoryDao.all.mapNotNull { it.data } .asSequence().filter { it.enabled }.map { it.id }.toList() sync(ids, request) } @@ -118,7 +119,7 @@ class SyncService : ConnectionService() { } fun setEnabled(repository: Repository, enabled: Boolean): Boolean { - Database.RepositoryAdapter.put(repository.enable(enabled)) + db.repositoryDao.put(repository.enable(enabled)) if (enabled) { if (repository.id != currentTask?.task?.repositoryId && !tasks.any { it.repositoryId == repository.id }) { tasks += Task(repository.id, true) @@ -137,10 +138,10 @@ class SyncService : ConnectionService() { } fun deleteRepository(repositoryId: Long): Boolean { - val repository = Database.RepositoryAdapter.get(repositoryId) + val repository = db.repositoryDao.get(repositoryId)?.data return repository != null && run { setEnabled(repository, false) - Database.RepositoryAdapter.markAsDeleted(repository.id) + db.repositoryDao.markAsDeleted(repository.id) true } } @@ -148,12 +149,14 @@ class SyncService : ConnectionService() { private val binder = Binder() override fun onBind(intent: Intent): Binder = binder + lateinit var db: DatabaseX private var stateDisposable: Disposable? = null override fun onCreate() { super.onCreate() + db = DatabaseX.getInstance(applicationContext) if (Android.sdk(26)) { NotificationChannel( NOTIFICATION_CHANNEL_SYNCING, @@ -330,7 +333,7 @@ class SyncService : ConnectionService() { if (currentTask == null) { if (tasks.isNotEmpty()) { val task = tasks.removeAt(0) - val repository = Database.RepositoryAdapter.get(task.repositoryId) + val repository = db.repositoryDao.get(task.repositoryId)?.data if (repository != null && repository.enabled) { val lastStarted = started val newStarted = @@ -374,7 +377,7 @@ class SyncService : ConnectionService() { if (hasUpdates && Preferences[Preferences.Key.UpdateNotify]) { val disposable = RxUtils .querySingle { it -> - Database.ProductAdapter + db.productDao .query( installed = true, updates = true, @@ -384,8 +387,7 @@ class SyncService : ConnectionService() { signal = it ) .use { - it.asSequence().map(Database.ProductAdapter::transformItem) - .toList() + it.asSequence().map { it.getProductItem() }.toList() } } .subscribeOn(Schedulers.io()) From cc8e7b6ea3be1707f42440084abcc8bea97b55e0 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Mon, 1 Nov 2021 01:07:21 +0100 Subject: [PATCH 15/17] 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}") - } - } - } } From f966630e8a7c68966aa7962bc42349cab4fd97aa Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Tue, 9 Nov 2021 23:36:50 +0100 Subject: [PATCH 16/17] Fix: Crashes caused by tables' names --- src/main/kotlin/com/looker/droidify/Common.kt | 4 ++-- .../kotlin/com/looker/droidify/database/DAOs.kt | 16 ++++++++-------- .../com/looker/droidify/database/Tables.kt | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/looker/droidify/Common.kt b/src/main/kotlin/com/looker/droidify/Common.kt index 6dca68af..a1471b0d 100644 --- a/src/main/kotlin/com/looker/droidify/Common.kt +++ b/src/main/kotlin/com/looker/droidify/Common.kt @@ -30,8 +30,8 @@ const val ROW_MATCH_RANK = "match_rank" const val ROW_REPOSITORY_NAME = "repository" const val ROW_PRODUCT_NAME = "product" const val ROW_CATEGORY_NAME = "category" -const val ROW_INSTALLED_NAME = "memory.installed" -const val ROW_LOCK_NAME = "memory.lock" +const val ROW_INSTALLED_NAME = "memory_installed" +const val ROW_LOCK_NAME = "memory_lock" const val JOB_ID_SYNC = 1 diff --git a/src/main/kotlin/com/looker/droidify/database/DAOs.kt b/src/main/kotlin/com/looker/droidify/database/DAOs.kt index 91c05098..774b410d 100644 --- a/src/main/kotlin/com/looker/droidify/database/DAOs.kt +++ b/src/main/kotlin/com/looker/droidify/database/DAOs.kt @@ -10,7 +10,7 @@ import com.looker.droidify.entity.ProductItem interface BaseDao { - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg product: T) @Update(onConflict = OnConflictStrategy.REPLACE) @@ -191,25 +191,25 @@ interface InstalledDao : BaseDao { } } - @Query("SELECT * FROM `memory.installed` WHERE package_name = :packageName") + @Query("SELECT * FROM memory_installed WHERE package_name = :packageName") fun get(packageName: String): Cursor - @Query("DELETE FROM 'memory.installed' WHERE package_name = :packageName") + @Query("DELETE FROM memory_installed WHERE package_name = :packageName") fun delete(packageName: String) } @Dao interface LockDao : BaseDao { - @Query("DELETE FROM 'memory.lock' WHERE package_name = :packageName") + @Query("DELETE FROM memory_lock WHERE package_name = :packageName") fun delete(packageName: String) } @Dao interface ProductTempDao : BaseDao { - @get:Query("SELECT * FROM `product.temporary`") + @get:Query("SELECT * FROM temporary_product") val all: Array - @Query("DELETE FROM `product.temporary`") + @Query("DELETE FROM temporary_product") fun emptyTable() @Insert @@ -249,9 +249,9 @@ interface ProductTempDao : BaseDao { @Dao interface CategoryTempDao : BaseDao { - @get:Query("SELECT * FROM `category.temporary`") + @get:Query("SELECT * FROM temporary_category") val all: Array - @Query("DELETE FROM `category.temporary`") + @Query("DELETE FROM temporary_category") fun emptyTable() } \ No newline at end of file diff --git a/src/main/kotlin/com/looker/droidify/database/Tables.kt b/src/main/kotlin/com/looker/droidify/database/Tables.kt index 3b881cdc..cda3236e 100644 --- a/src/main/kotlin/com/looker/droidify/database/Tables.kt +++ b/src/main/kotlin/com/looker/droidify/database/Tables.kt @@ -50,7 +50,7 @@ open class Product { var data_item: ProductItem? = null } -@Entity(tableName = "product.temporary") +@Entity(tableName = "temporary_product") class ProductTemp : Product() @Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"]) @@ -60,10 +60,10 @@ open class Category { var name = "" } -@Entity(tableName = "category.temporary") +@Entity(tableName = "temporary_category") class CategoryTemp : Category() -@Entity(tableName = "memory.installed") +@Entity(tableName = "memory_installed") class Installed(pName: String = "") { @PrimaryKey var package_name = pName @@ -73,7 +73,7 @@ class Installed(pName: String = "") { var signature = "" } -@Entity(tableName = "memory.lock") +@Entity(tableName = "memory_lock") class Lock { @PrimaryKey var package_name = "" From 501e46d65733ab0c88637b3aadd4bec8e98b5b17 Mon Sep 17 00:00:00 2001 From: machiav3lli Date: Wed, 10 Nov 2021 00:57:24 +0100 Subject: [PATCH 17/17] Update: Remove Database class (4.8/5 in replacing SQLite with Room) --- .../com/looker/droidify/database/Database.kt | 318 ------------------ .../droidify/index/RepositoryUpdater.kt | 3 +- .../droidify/screen/EditRepositoryFragment.kt | 3 +- .../looker/droidify/screen/ProductFragment.kt | 3 +- .../droidify/screen/ProductsFragment.kt | 3 +- .../droidify/screen/ScreenshotsFragment.kt | 3 +- .../looker/droidify/screen/TabsFragment.kt | 5 +- 7 files changed, 7 insertions(+), 331 deletions(-) delete mode 100644 src/main/kotlin/com/looker/droidify/database/Database.kt 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())