mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +00:00
Remove: Old database class
This commit is contained in:
parent
b359475db7
commit
5db9aa2591
@ -1,828 +0,0 @@
|
|||||||
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 {
|
|
||||||
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
|
|
||||||
|
|
||||||
private interface Table {
|
|
||||||
val memory: Boolean
|
|
||||||
val innerName: String
|
|
||||||
val createTable: String
|
|
||||||
val createIndex: String?
|
|
||||||
get() = null
|
|
||||||
|
|
||||||
val databasePrefix: String
|
|
||||||
get() = if (memory) "memory." else ""
|
|
||||||
|
|
||||||
val name: String
|
|
||||||
get() = "$databasePrefix$innerName"
|
|
||||||
|
|
||||||
fun formatCreateTable(name: String): String {
|
|
||||||
return "CREATE TABLE $name (${QueryBuilder.trimQuery(createTable)})"
|
|
||||||
}
|
|
||||||
|
|
||||||
val createIndexPairFormatted: Pair<String, String>?
|
|
||||||
get() = createIndex?.let {
|
|
||||||
Pair(
|
|
||||||
"CREATE INDEX ${innerName}_index ON $innerName ($it)",
|
|
||||||
"CREATE INDEX ${name}_index ON $innerName ($it)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object Schema {
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
object Synthetic {
|
|
||||||
const val ROW_CAN_UPDATE = "can_update"
|
|
||||||
const val ROW_MATCH_RANK = "match_rank"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) {
|
|
||||||
val tables = db.query(
|
|
||||||
"sqlite_master", columns = arrayOf("name"),
|
|
||||||
selection = Pair("type = ?", arrayOf("table"))
|
|
||||||
)
|
|
||||||
.use { it.asSequence().mapNotNull { it.getString(0) }.toList() }
|
|
||||||
.filter { !it.startsWith("sqlite_") && !it.startsWith("android_") }
|
|
||||||
.toSet() - neededTables.mapNotNull { if (it.memory) null else it.name }
|
|
||||||
if (tables.isNotEmpty()) {
|
|
||||||
for (table in tables) {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS $table")
|
|
||||||
}
|
|
||||||
if (!db.inTransaction()) {
|
|
||||||
db.execSQL("VACUUM")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Subject {
|
|
||||||
object Repositories : Subject()
|
|
||||||
data class Repository(val id: Long) : Subject()
|
|
||||||
object Products : Subject()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val observers = mutableMapOf<Subject, MutableSet<() -> Unit>>()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun observable(subject: Subject): Observable<Unit> {
|
|
||||||
return Observable.create {
|
|
||||||
val callback: () -> Unit = { it.onNext(Unit) }
|
|
||||||
val dataObservable = dataObservable(subject)
|
|
||||||
dataObservable(true, callback)
|
|
||||||
it.setCancellable { dataObservable(false, callback) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notifyChanged(vararg subjects: Subject) {
|
|
||||||
synchronized(observers) {
|
|
||||||
subjects.asSequence().mapNotNull { observers[it] }.flatten().forEach { it() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String>? = null,
|
|
||||||
selection: Pair<String, Array<String>>? = null, orderBy: String? = null,
|
|
||||||
signal: CancellationSignal? = null,
|
|
||||||
): Cursor {
|
|
||||||
return query(
|
|
||||||
false,
|
|
||||||
table,
|
|
||||||
columns,
|
|
||||||
selection?.first,
|
|
||||||
selection?.second,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
orderBy,
|
|
||||||
null,
|
|
||||||
signal
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Cursor.observable(subject: Subject): ObservableCursor {
|
|
||||||
return ObservableCursor(this, dataObservable(subject))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Repository> {
|
|
||||||
return db.query(
|
|
||||||
Schema.Repository.name,
|
|
||||||
selection = Pair("${Schema.Repository.ROW_DELETED} == 0", emptyArray()),
|
|
||||||
signal = signal
|
|
||||||
).use { it.asSequence().map(::transform).toList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done Pair<Long,Int> instead
|
|
||||||
// MAYBE signal has to be considered
|
|
||||||
fun getAllDisabledDeleted(signal: CancellationSignal?): Set<Pair<Long, Boolean>> {
|
|
||||||
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<Pair<Long, Boolean>>) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unnecessary with Room
|
|
||||||
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<Product> {
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complex left to wiring phase
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unnecessary with Room
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unnecessary with Room
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object CategoryAdapter {
|
|
||||||
// Done
|
|
||||||
fun getAll(signal: CancellationSignal?): Set<String> {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<InstalledItem>) {
|
|
||||||
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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object LockAdapter {
|
|
||||||
// Done in insert (Lock object instead of pair)
|
|
||||||
private fun put(lock: Pair<String, Long>, 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<String, Long>) = put(lock, true)
|
|
||||||
|
|
||||||
// Done in insert (Lock object instead of pair)
|
|
||||||
fun putAll(locks: List<Pair<String, Long>>) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add temporary tables
|
|
||||||
object UpdaterAdapter {
|
|
||||||
private val Table.temporaryName: String
|
|
||||||
get() = "${name}_temporary"
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putTemporary(products: List<Product>) {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,10 +20,10 @@ import androidx.navigation.ui.setupWithNavController
|
|||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.looker.droidify.BuildConfig
|
import com.looker.droidify.BuildConfig
|
||||||
import com.looker.droidify.ContextWrapperX
|
import com.looker.droidify.ContextWrapperX
|
||||||
|
import com.looker.droidify.MainApplication
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.content.Preferences
|
import com.looker.droidify.content.Preferences
|
||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.database.QueryLoader
|
import com.looker.droidify.database.QueryLoader
|
||||||
import com.looker.droidify.databinding.ActivityMainXBinding
|
import com.looker.droidify.databinding.ActivityMainXBinding
|
||||||
import com.looker.droidify.installer.AppInstaller
|
import com.looker.droidify.installer.AppInstaller
|
||||||
@ -67,6 +67,9 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
val db
|
||||||
|
get() = (application as MainApplication).db
|
||||||
|
|
||||||
lateinit var cursorOwner: CursorOwner
|
lateinit var cursorOwner: CursorOwner
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@ -264,7 +267,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor>
|
|||||||
val request = viewModel.activeRequests[id]!!.request
|
val request = viewModel.activeRequests[id]!!.request
|
||||||
return QueryLoader(this) {
|
return QueryLoader(this) {
|
||||||
when (request) {
|
when (request) {
|
||||||
is CursorOwner.Request.ProductsAvailable -> Database.ProductAdapter
|
is CursorOwner.Request.ProductsAvailable -> db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = false,
|
installed = false,
|
||||||
updates = false,
|
updates = false,
|
||||||
@ -273,7 +276,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor>
|
|||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
is CursorOwner.Request.ProductsInstalled -> Database.ProductAdapter
|
is CursorOwner.Request.ProductsInstalled -> db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = true,
|
installed = true,
|
||||||
updates = false,
|
updates = false,
|
||||||
@ -282,7 +285,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor>
|
|||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
is CursorOwner.Request.ProductsUpdates -> Database.ProductAdapter
|
is CursorOwner.Request.ProductsUpdates -> db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = true,
|
installed = true,
|
||||||
updates = true,
|
updates = true,
|
||||||
@ -291,7 +294,7 @@ class MainActivityX : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor>
|
|||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
is CursorOwner.Request.Repositories -> Database.RepositoryAdapter.query(it)
|
is CursorOwner.Request.Repositories -> db.repositoryDao.allCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.databinding.FragmentExploreXBinding
|
import com.looker.droidify.databinding.FragmentExploreXBinding
|
||||||
import com.looker.droidify.ui.adapters.AppListAdapter
|
import com.looker.droidify.ui.adapters.AppListAdapter
|
||||||
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
||||||
@ -60,9 +59,9 @@ class ExploreFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
|
|
||||||
mainActivityX.attachCursorOwner(this, viewModel.request(source))
|
mainActivityX.attachCursorOwner(this, viewModel.request(source))
|
||||||
repositoriesDisposable = Observable.just(Unit)
|
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())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
.flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } }
|
||||||
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it }
|
.subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it }
|
||||||
|
@ -12,7 +12,6 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.databinding.FragmentInstalledXBinding
|
import com.looker.droidify.databinding.FragmentInstalledXBinding
|
||||||
import com.looker.droidify.ui.adapters.AppListAdapter
|
import com.looker.droidify.ui.adapters.AppListAdapter
|
||||||
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
||||||
@ -58,9 +57,9 @@ class InstalledFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
|
|
||||||
mainActivityX.attachCursorOwner(this, viewModel.request(source))
|
mainActivityX.attachCursorOwner(this, viewModel.request(source))
|
||||||
repositoriesDisposable = Observable.just(Unit)
|
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())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
.flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } }
|
||||||
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it }
|
.subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it }
|
||||||
|
@ -12,7 +12,6 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.CursorOwner
|
import com.looker.droidify.database.CursorOwner
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.databinding.FragmentLatestXBinding
|
import com.looker.droidify.databinding.FragmentLatestXBinding
|
||||||
import com.looker.droidify.ui.adapters.AppListAdapter
|
import com.looker.droidify.ui.adapters.AppListAdapter
|
||||||
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
import com.looker.droidify.ui.viewmodels.MainNavFragmentViewModelX
|
||||||
@ -59,9 +58,9 @@ class LatestFragment : MainNavFragmentX(), CursorOwner.Callback {
|
|||||||
|
|
||||||
mainActivityX.attachCursorOwner(this, viewModel.request(source))
|
mainActivityX.attachCursorOwner(this, viewModel.request(source))
|
||||||
repositoriesDisposable = Observable.just(Unit)
|
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())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
|
.flatMapSingle { RxUtils.querySingle { mainActivityX.db.repositoryDao.all.mapNotNull { it.data } } }
|
||||||
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it }
|
.subscribe { (binding.recyclerView.adapter as? AppListAdapter)?.repositories = it }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user