mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-06-21 06:39:19 +00:00
Merge DB migration branch (Warning: possible conflict solving failures)
This commit is contained in:
@ -87,9 +87,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
|
||||
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<Cursor> {
|
||||
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<Cursor> {
|
||||
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<Cursor> {
|
||||
order = request.order,
|
||||
signal = it
|
||||
)
|
||||
is Request.Repositories -> Database.RepositoryAdapter.query(it)
|
||||
is Request.Repositories -> db.repositoryDao.allCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +1,171 @@
|
||||
package com.looker.droidify.database
|
||||
|
||||
import android.database.SQLException
|
||||
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
|
||||
|
||||
@Dao
|
||||
interface RepositoryDao {
|
||||
@Insert
|
||||
@Throws(SQLException::class)
|
||||
fun insert(vararg repository: Repository)
|
||||
|
||||
interface BaseDao<T> {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg product: T)
|
||||
|
||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun update(vararg repository: Repository?)
|
||||
fun update(vararg obj: T): Int
|
||||
|
||||
fun put(repository: Repository) {
|
||||
if (repository.id >= 0L) update(repository) else insert(repository)
|
||||
@Delete
|
||||
fun delete(obj: T)
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface RepositoryDao : BaseDao<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 = false
|
||||
data = it
|
||||
}
|
||||
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<Repository>
|
||||
|
||||
@get:Query("SELECT _id, deleted FROM repository WHERE deleted != 0 and enabled == 0 ORDER BY _id ASC")
|
||||
val allDisabledDeleted: List<Repository.IdAndDeleted>
|
||||
|
||||
@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 = true }?.let { update(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface ProductDao {
|
||||
interface ProductDao : BaseDao<Product> {
|
||||
@Query("SELECT COUNT(*) FROM product WHERE repository_id = :id")
|
||||
fun countForRepository(id: Long): Long
|
||||
|
||||
@Query("SELECT * FROM product WHERE package_name = :packageName")
|
||||
fun get(packageName: String): Product?
|
||||
fun get(packageName: String): List<Product?>
|
||||
|
||||
@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
|
||||
interface CategoryDao {
|
||||
@Query(
|
||||
interface CategoryDao : BaseDao<Category> {
|
||||
@get:Query(
|
||||
"""SELECT DISTINCT category.name
|
||||
FROM category AS category
|
||||
JOIN repository AS repository
|
||||
@ -59,31 +173,85 @@ interface CategoryDao {
|
||||
WHERE repository.enabled != 0 AND
|
||||
repository.deleted == 0"""
|
||||
)
|
||||
fun getAll(): List<String>
|
||||
val allNames: List<String>
|
||||
|
||||
@Query("DELETE FROM category WHERE repository_id = :id")
|
||||
fun deleteById(vararg id: Long): Int
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface InstalledDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
@Throws(SQLException::class)
|
||||
fun insert(vararg installed: Installed)
|
||||
interface InstalledDao : BaseDao<Installed> {
|
||||
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 installed WHERE package_name = :packageName")
|
||||
fun get(packageName: String): Installed?
|
||||
@Query("SELECT * FROM memory_installed WHERE package_name = :packageName")
|
||||
fun get(packageName: String): Cursor
|
||||
|
||||
@Query("DELETE FROM installed WHERE package_name = :packageName")
|
||||
@Query("DELETE FROM memory_installed WHERE package_name = :packageName")
|
||||
fun delete(packageName: String)
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface LockDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
@Throws(SQLException::class)
|
||||
fun insert(vararg lock: Lock)
|
||||
|
||||
@Query("DELETE FROM lock WHERE package_name = :packageName")
|
||||
interface LockDao : BaseDao<Lock> {
|
||||
@Query("DELETE FROM memory_lock WHERE package_name = :packageName")
|
||||
fun delete(packageName: String)
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface ProductTempDao : BaseDao<ProductTemp> {
|
||||
@get:Query("SELECT * FROM temporary_product")
|
||||
val all: Array<ProductTemp>
|
||||
|
||||
@Query("DELETE FROM temporary_product")
|
||||
fun emptyTable()
|
||||
|
||||
@Insert
|
||||
fun insertCategory(vararg product: CategoryTemp)
|
||||
|
||||
@Transaction
|
||||
fun putTemporary(products: List<com.looker.droidify.entity.Product>) {
|
||||
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<CategoryTemp> {
|
||||
@get:Query("SELECT * FROM temporary_category")
|
||||
val all: Array<CategoryTemp>
|
||||
|
||||
@Query("DELETE FROM temporary_category")
|
||||
fun emptyTable()
|
||||
}
|
@ -10,7 +10,9 @@ import androidx.room.TypeConverters
|
||||
entities = [
|
||||
Repository::class,
|
||||
Product::class,
|
||||
ProductTemp::class,
|
||||
Category::class,
|
||||
CategoryTemp::class,
|
||||
Installed::class,
|
||||
Lock::class
|
||||
], version = 1
|
||||
@ -19,7 +21,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
|
||||
|
||||
@ -46,17 +50,33 @@ abstract class DatabaseX : RoomDatabase() {
|
||||
}
|
||||
|
||||
fun cleanUp(pairs: Set<Pair<Long, Boolean>>) {
|
||||
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 }) {
|
||||
com.looker.droidify.database.Database.notifyChanged(com.looker.droidify.database.Database.Subject.Products)
|
||||
}*/
|
||||
}
|
||||
|
||||
fun finishTemporary(repository: com.looker.droidify.entity.Repository, success: Boolean) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +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.Product
|
||||
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 {
|
||||
@ -17,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
|
||||
@ -26,54 +25,60 @@ class Repository {
|
||||
@ColumnInfo(name = "_id")
|
||||
var id = 0L
|
||||
|
||||
var deleted = 0
|
||||
var deleted = false
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(primaryKeys = ["repository_id", "package_name"])
|
||||
class Product {
|
||||
var repository_id: Long = 0
|
||||
@Entity(tableName = "product", primaryKeys = ["repository_id", "package_name"])
|
||||
open class Product {
|
||||
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(primaryKeys = ["repository_id", "package_name", "name"])
|
||||
class Category {
|
||||
@Entity(tableName = "temporary_product")
|
||||
class ProductTemp : Product()
|
||||
|
||||
@Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"])
|
||||
open class Category {
|
||||
var repository_id: Long = 0
|
||||
var package_name = ""
|
||||
var name = ""
|
||||
}
|
||||
|
||||
@Entity
|
||||
class Installed {
|
||||
@Entity(tableName = "temporary_category")
|
||||
class CategoryTemp : Category()
|
||||
|
||||
@Entity(tableName = "memory_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 = ""
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Entity(tableName = "memory_lock")
|
||||
class Lock {
|
||||
@PrimaryKey
|
||||
var package_name = ""
|
||||
|
||||
var version_code = 0
|
||||
var version_code = 0L
|
||||
}
|
||||
|
||||
object Converters {
|
||||
@ -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
|
||||
|
Reference in New Issue
Block a user