mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 03:12:15 +00:00
Merge DB migration branch (Warning: possible conflict solving failures)
This commit is contained in:
commit
5386c9d843
@ -25,6 +25,13 @@ android {
|
|||||||
versionCode = 43
|
versionCode = 43
|
||||||
versionName = "0.4.3"
|
versionName = "0.4.3"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
|
javaCompileOptions {
|
||||||
|
annotationProcessorOptions {
|
||||||
|
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||||
|
arguments += ["room.incremental": "true"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets.all {
|
sourceSets.all {
|
||||||
|
@ -1,13 +1,39 @@
|
|||||||
package com.looker.droidify
|
package com.looker.droidify
|
||||||
|
|
||||||
object Common {
|
const val NOTIFICATION_CHANNEL_SYNCING = "syncing"
|
||||||
const val NOTIFICATION_CHANNEL_SYNCING = "syncing"
|
const val NOTIFICATION_CHANNEL_UPDATES = "updates"
|
||||||
const val NOTIFICATION_CHANNEL_UPDATES = "updates"
|
const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading"
|
||||||
const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading"
|
|
||||||
|
|
||||||
const val NOTIFICATION_ID_SYNCING = 1
|
const val NOTIFICATION_ID_SYNCING = 1
|
||||||
const val NOTIFICATION_ID_UPDATES = 2
|
const val NOTIFICATION_ID_UPDATES = 2
|
||||||
const val NOTIFICATION_ID_DOWNLOADING = 3
|
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
|
||||||
|
|
||||||
const val PREFS_LANGUAGE = "languages"
|
const val PREFS_LANGUAGE = "languages"
|
||||||
const val PREFS_LANGUAGE_DEFAULT = "system"
|
const val PREFS_LANGUAGE_DEFAULT = "system"
|
||||||
|
@ -10,7 +10,7 @@ import coil.ImageLoaderFactory
|
|||||||
import com.looker.droidify.content.Cache
|
import com.looker.droidify.content.Cache
|
||||||
import com.looker.droidify.content.Preferences
|
import com.looker.droidify.content.Preferences
|
||||||
import com.looker.droidify.content.ProductPreferences
|
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.index.RepositoryUpdater
|
||||||
import com.looker.droidify.network.CoilDownloader
|
import com.looker.droidify.network.CoilDownloader
|
||||||
import com.looker.droidify.network.Downloader
|
import com.looker.droidify.network.Downloader
|
||||||
@ -29,19 +29,21 @@ import java.net.Proxy
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class MainApplication : Application(), ImageLoaderFactory {
|
class MainApplication : Application(), ImageLoaderFactory {
|
||||||
|
|
||||||
|
lateinit var db: DatabaseX
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
val databaseUpdated = Database.init(this)
|
db = DatabaseX.getInstance(applicationContext)
|
||||||
Preferences.init(this)
|
Preferences.init(this)
|
||||||
ProductPreferences.init(this)
|
ProductPreferences.init(this)
|
||||||
RepositoryUpdater.init()
|
RepositoryUpdater.init(this)
|
||||||
listenApplications()
|
listenApplications()
|
||||||
listenPreferences()
|
listenPreferences()
|
||||||
|
|
||||||
if (databaseUpdated) {
|
/*if (databaseUpdated) {
|
||||||
forceSyncAll()
|
forceSyncAll()
|
||||||
}
|
}*/
|
||||||
|
|
||||||
Cache.cleanup(this)
|
Cache.cleanup(this)
|
||||||
updateSyncJob(false)
|
updateSyncJob(false)
|
||||||
@ -66,9 +68,9 @@ class MainApplication : Application(), ImageLoaderFactory {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (packageInfo != null) {
|
if (packageInfo != null) {
|
||||||
Database.InstalledAdapter.put(packageInfo.toInstalledItem())
|
db.installedDao.put(packageInfo.toInstalledItem())
|
||||||
} else {
|
} else {
|
||||||
Database.InstalledAdapter.delete(packageName)
|
db.installedDao.delete(packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +84,7 @@ class MainApplication : Application(), ImageLoaderFactory {
|
|||||||
val installedItems =
|
val installedItems =
|
||||||
packageManager.getInstalledPackages(Android.PackageManager.signaturesFlag)
|
packageManager.getInstalledPackages(Android.PackageManager.signaturesFlag)
|
||||||
.map { it.toInstalledItem() }
|
.map { it.toInstalledItem() }
|
||||||
Database.InstalledAdapter.putAll(installedItems)
|
db.installedDao.put(*installedItems.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listenPreferences() {
|
private fun listenPreferences() {
|
||||||
@ -125,19 +127,19 @@ class MainApplication : Application(), ImageLoaderFactory {
|
|||||||
|
|
||||||
private fun updateSyncJob(force: Boolean) {
|
private fun updateSyncJob(force: Boolean) {
|
||||||
val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
|
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) {
|
if (reschedule) {
|
||||||
val autoSync = Preferences[Preferences.Key.AutoSync]
|
val autoSync = Preferences[Preferences.Key.AutoSync]
|
||||||
when (autoSync) {
|
when (autoSync) {
|
||||||
Preferences.AutoSync.Never -> {
|
Preferences.AutoSync.Never -> {
|
||||||
jobScheduler.cancel(Common.JOB_ID_SYNC)
|
jobScheduler.cancel(JOB_ID_SYNC)
|
||||||
}
|
}
|
||||||
Preferences.AutoSync.Wifi, Preferences.AutoSync.Always -> {
|
Preferences.AutoSync.Wifi, Preferences.AutoSync.Always -> {
|
||||||
val period = 12 * 60 * 60 * 1000L // 12 hours
|
val period = 12 * 60 * 60 * 1000L // 12 hours
|
||||||
val wifiOnly = autoSync == Preferences.AutoSync.Wifi
|
val wifiOnly = autoSync == Preferences.AutoSync.Wifi
|
||||||
jobScheduler.schedule(JobInfo
|
jobScheduler.schedule(JobInfo
|
||||||
.Builder(
|
.Builder(
|
||||||
Common.JOB_ID_SYNC,
|
JOB_ID_SYNC,
|
||||||
ComponentName(this, SyncService.Job::class.java)
|
ComponentName(this, SyncService.Job::class.java)
|
||||||
)
|
)
|
||||||
.setRequiredNetworkType(if (wifiOnly) JobInfo.NETWORK_TYPE_UNMETERED else JobInfo.NETWORK_TYPE_ANY)
|
.setRequiredNetworkType(if (wifiOnly) JobInfo.NETWORK_TYPE_UNMETERED else JobInfo.NETWORK_TYPE_ANY)
|
||||||
@ -181,9 +183,9 @@ class MainApplication : Application(), ImageLoaderFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun forceSyncAll() {
|
private fun forceSyncAll() {
|
||||||
Database.RepositoryAdapter.getAll(null).forEach {
|
db.repositoryDao.all.mapNotNull { it.data }.forEach {
|
||||||
if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) {
|
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 ->
|
Connection(SyncService::class.java, onBind = { connection, binder ->
|
||||||
|
@ -2,7 +2,8 @@ package com.looker.droidify.content
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
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.entity.ProductPreference
|
||||||
import com.looker.droidify.utility.extension.json.Json
|
import com.looker.droidify.utility.extension.json.Json
|
||||||
import com.looker.droidify.utility.extension.json.parseDictionary
|
import com.looker.droidify.utility.extension.json.parseDictionary
|
||||||
@ -21,17 +22,30 @@ object ProductPreferences {
|
|||||||
private lateinit var preferences: SharedPreferences
|
private lateinit var preferences: SharedPreferences
|
||||||
private val mutableSubject = MutableSharedFlow<Pair<String, Long?>>()
|
private val mutableSubject = MutableSharedFlow<Pair<String, Long?>>()
|
||||||
private val subject = mutableSubject.asSharedFlow()
|
private val subject = mutableSubject.asSharedFlow()
|
||||||
|
lateinit var db: DatabaseX
|
||||||
|
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
|
db = DatabaseX.getInstance(context)
|
||||||
preferences = context.getSharedPreferences("product_preferences", Context.MODE_PRIVATE)
|
preferences = context.getSharedPreferences("product_preferences", Context.MODE_PRIVATE)
|
||||||
Database.LockAdapter.putAll(preferences.all.keys
|
db.lockDao.insert(*preferences.all.keys
|
||||||
.mapNotNull { packageName ->
|
.mapNotNull { pName ->
|
||||||
this[packageName].databaseVersionCode?.let { Pair(packageName, it) }
|
this[pName].databaseVersionCode?.let {
|
||||||
})
|
Lock().apply {
|
||||||
|
package_name = pName
|
||||||
|
version_code = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toTypedArray()
|
||||||
|
)
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
subject.collect { (packageName, versionCode) ->
|
subject.collect { (packageName, versionCode) ->
|
||||||
if (versionCode != null) Database.LockAdapter.put(Pair(packageName, versionCode))
|
if (versionCode != null) db.lockDao.insert(Lock().apply {
|
||||||
else Database.LockAdapter.delete(packageName)
|
package_name = pName
|
||||||
|
version_code = versionCode
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else db.lockDao.delete(pName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,9 +87,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
|
|
||||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
|
||||||
val request = activeRequests[id]!!.request
|
val request = activeRequests[id]!!.request
|
||||||
|
val db = DatabaseX.getInstance(requireContext())
|
||||||
return QueryLoader(requireContext()) {
|
return QueryLoader(requireContext()) {
|
||||||
when (request) {
|
when (request) {
|
||||||
is Request.ProductsAvailable -> Database.ProductAdapter
|
is Request.ProductsAvailable -> db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = false,
|
installed = false,
|
||||||
updates = false,
|
updates = false,
|
||||||
@ -98,7 +99,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
is Request.ProductsInstalled -> Database.ProductAdapter
|
is Request.ProductsInstalled -> db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = true,
|
installed = true,
|
||||||
updates = false,
|
updates = false,
|
||||||
@ -107,7 +108,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
is Request.ProductsUpdates -> Database.ProductAdapter
|
is Request.ProductsUpdates -> db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = true,
|
installed = true,
|
||||||
updates = true,
|
updates = true,
|
||||||
@ -116,7 +117,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
is Request.Repositories -> Database.RepositoryAdapter.query(it)
|
is Request.Repositories -> db.repositoryDao.allCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,171 @@
|
|||||||
package com.looker.droidify.database
|
package com.looker.droidify.database
|
||||||
|
|
||||||
import android.database.SQLException
|
import android.database.Cursor
|
||||||
|
import android.os.CancellationSignal
|
||||||
import androidx.room.*
|
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 {
|
interface BaseDao<T> {
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
@Throws(SQLException::class)
|
fun insert(vararg product: T)
|
||||||
fun insert(vararg repository: Repository)
|
|
||||||
|
|
||||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun update(vararg repository: Repository?)
|
fun update(vararg obj: T): Int
|
||||||
|
|
||||||
fun put(repository: Repository) {
|
@Delete
|
||||||
if (repository.id >= 0L) update(repository) else insert(repository)
|
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")
|
@Query("SELECT * FROM repository WHERE _id = :id and deleted == 0")
|
||||||
fun get(id: Long): Repository?
|
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")
|
@get:Query("SELECT * FROM repository WHERE deleted == 0 ORDER BY _id ASC")
|
||||||
val all: List<Repository>
|
val all: List<Repository>
|
||||||
|
|
||||||
@get:Query("SELECT _id, deleted FROM repository WHERE deleted != 0 and enabled == 0 ORDER BY _id ASC")
|
@get:Query("SELECT _id, deleted FROM repository WHERE deleted != 0 and enabled == 0 ORDER BY _id ASC")
|
||||||
val allDisabledDeleted: List<Repository.IdAndDeleted>
|
val allDisabledDeleted: List<Repository.IdAndDeleted>
|
||||||
|
|
||||||
@Delete
|
|
||||||
fun delete(repository: Repository)
|
|
||||||
|
|
||||||
@Query("DELETE FROM repository WHERE _id = :id")
|
@Query("DELETE FROM repository WHERE _id = :id")
|
||||||
fun deleteById(vararg id: Long): Int
|
fun deleteById(vararg id: Long): Int
|
||||||
|
|
||||||
|
// TODO optimize
|
||||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun markAsDeleted(id: Long) {
|
fun markAsDeleted(id: Long) {
|
||||||
update(get(id).apply { this?.deleted = 1 })
|
get(id).apply { this?.deleted = true }?.let { update(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface ProductDao {
|
interface ProductDao : BaseDao<Product> {
|
||||||
@Query("SELECT COUNT(*) FROM product WHERE repository_id = :id")
|
@Query("SELECT COUNT(*) FROM product WHERE repository_id = :id")
|
||||||
fun countForRepository(id: Long): Long
|
fun countForRepository(id: Long): Long
|
||||||
|
|
||||||
@Query("SELECT * FROM product WHERE package_name = :packageName")
|
@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")
|
@Query("DELETE FROM product WHERE repository_id = :id")
|
||||||
fun deleteById(vararg id: Long): Int
|
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
|
@Dao
|
||||||
interface CategoryDao {
|
interface CategoryDao : BaseDao<Category> {
|
||||||
@Query(
|
@get:Query(
|
||||||
"""SELECT DISTINCT category.name
|
"""SELECT DISTINCT category.name
|
||||||
FROM category AS category
|
FROM category AS category
|
||||||
JOIN repository AS repository
|
JOIN repository AS repository
|
||||||
@ -59,31 +173,85 @@ interface CategoryDao {
|
|||||||
WHERE repository.enabled != 0 AND
|
WHERE repository.enabled != 0 AND
|
||||||
repository.deleted == 0"""
|
repository.deleted == 0"""
|
||||||
)
|
)
|
||||||
fun getAll(): List<String>
|
val allNames: List<String>
|
||||||
|
|
||||||
@Query("DELETE FROM category WHERE repository_id = :id")
|
@Query("DELETE FROM category WHERE repository_id = :id")
|
||||||
fun deleteById(vararg id: Long): Int
|
fun deleteById(vararg id: Long): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface InstalledDao {
|
interface InstalledDao : BaseDao<Installed> {
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
fun put(vararg isntalled: com.looker.droidify.entity.InstalledItem) {
|
||||||
@Throws(SQLException::class)
|
isntalled.forEach {
|
||||||
fun insert(vararg installed: Installed)
|
insert(Installed(it.packageName).apply {
|
||||||
|
version = it.version
|
||||||
|
version_code = it.versionCode
|
||||||
|
signature = it.signature
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Query("SELECT * FROM installed WHERE package_name = :packageName")
|
@Query("SELECT * FROM memory_installed WHERE package_name = :packageName")
|
||||||
fun get(packageName: String): Installed?
|
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)
|
fun delete(packageName: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface LockDao {
|
interface LockDao : BaseDao<Lock> {
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Query("DELETE FROM memory_lock WHERE package_name = :packageName")
|
||||||
@Throws(SQLException::class)
|
|
||||||
fun insert(vararg lock: Lock)
|
|
||||||
|
|
||||||
@Query("DELETE FROM lock WHERE package_name = :packageName")
|
|
||||||
fun delete(packageName: String)
|
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 = [
|
entities = [
|
||||||
Repository::class,
|
Repository::class,
|
||||||
Product::class,
|
Product::class,
|
||||||
|
ProductTemp::class,
|
||||||
Category::class,
|
Category::class,
|
||||||
|
CategoryTemp::class,
|
||||||
Installed::class,
|
Installed::class,
|
||||||
Lock::class
|
Lock::class
|
||||||
], version = 1
|
], version = 1
|
||||||
@ -19,7 +21,9 @@ import androidx.room.TypeConverters
|
|||||||
abstract class DatabaseX : RoomDatabase() {
|
abstract class DatabaseX : RoomDatabase() {
|
||||||
abstract val repositoryDao: RepositoryDao
|
abstract val repositoryDao: RepositoryDao
|
||||||
abstract val productDao: ProductDao
|
abstract val productDao: ProductDao
|
||||||
|
abstract val productTempDao: ProductTempDao
|
||||||
abstract val categoryDao: CategoryDao
|
abstract val categoryDao: CategoryDao
|
||||||
|
abstract val categoryTempDao: CategoryTempDao
|
||||||
abstract val installedDao: InstalledDao
|
abstract val installedDao: InstalledDao
|
||||||
abstract val lockDao: LockDao
|
abstract val lockDao: LockDao
|
||||||
|
|
||||||
@ -46,6 +50,7 @@ abstract class DatabaseX : RoomDatabase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cleanUp(pairs: Set<Pair<Long, Boolean>>) {
|
fun cleanUp(pairs: Set<Pair<Long, Boolean>>) {
|
||||||
|
runInTransaction {
|
||||||
val result = pairs.windowed(10, 10, true).map {
|
val result = pairs.windowed(10, 10, true).map {
|
||||||
val ids = it.map { it.first }.toLongArray()
|
val ids = it.map { it.first }.toLongArray()
|
||||||
val productsCount = productDao.deleteById(*ids)
|
val productsCount = productDao.deleteById(*ids)
|
||||||
@ -54,9 +59,24 @@ abstract class DatabaseX : RoomDatabase() {
|
|||||||
repositoryDao.deleteById(*deleteIds)
|
repositoryDao.deleteById(*deleteIds)
|
||||||
productsCount != 0 || categoriesCount != 0
|
productsCount != 0 || categoriesCount != 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Use live objects and observers instead
|
// Use live objects and observers instead
|
||||||
/*if (result.any { it }) {
|
/*if (result.any { it }) {
|
||||||
com.looker.droidify.database.Database.notifyChanged(com.looker.droidify.database.Database.Subject.Products)
|
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
|
this.arguments += arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun build() = builder.toString()
|
||||||
|
|
||||||
fun query(db: SQLiteDatabase, signal: CancellationSignal?): Cursor {
|
fun query(db: SQLiteDatabase, signal: CancellationSignal?): Cursor {
|
||||||
val query = builder.toString()
|
val query = builder.toString()
|
||||||
val arguments = arguments.toTypedArray()
|
val arguments = arguments.toTypedArray()
|
||||||
|
@ -4,11 +4,10 @@ import androidx.room.ColumnInfo
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import androidx.room.TypeConverter
|
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.ProductItem
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
|
import com.looker.droidify.utility.jsonGenerate
|
||||||
|
import com.looker.droidify.utility.jsonParse
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
class Repository {
|
class Repository {
|
||||||
@ -17,7 +16,7 @@ class Repository {
|
|||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
|
|
||||||
var enabled = 0
|
var enabled = 0
|
||||||
var deleted = 0
|
var deleted = false
|
||||||
|
|
||||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
var data: Repository? = null
|
var data: Repository? = null
|
||||||
@ -26,54 +25,60 @@ class Repository {
|
|||||||
@ColumnInfo(name = "_id")
|
@ColumnInfo(name = "_id")
|
||||||
var id = 0L
|
var id = 0L
|
||||||
|
|
||||||
var deleted = 0
|
var deleted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(primaryKeys = ["repository_id", "package_name"])
|
@Entity(tableName = "product", primaryKeys = ["repository_id", "package_name"])
|
||||||
class Product {
|
open class Product {
|
||||||
var repository_id: Long = 0
|
var repository_id = 0L
|
||||||
var package_name = ""
|
var package_name = ""
|
||||||
|
|
||||||
var name = ""
|
var name = ""
|
||||||
var summary = ""
|
var summary = ""
|
||||||
var description = ""
|
var description = ""
|
||||||
var added = 0
|
var added = 0L
|
||||||
var updated = 0
|
var updated = 0L
|
||||||
var version_code = 0
|
var version_code = 0L
|
||||||
var signatures = ""
|
var signatures = ""
|
||||||
var compatible = 0
|
var compatible = 0
|
||||||
|
|
||||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
var data: Product? = null
|
var data: com.looker.droidify.entity.Product? = null
|
||||||
|
|
||||||
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
|
||||||
var data_item: ProductItem? = null
|
var data_item: ProductItem? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(primaryKeys = ["repository_id", "package_name", "name"])
|
@Entity(tableName = "temporary_product")
|
||||||
class Category {
|
class ProductTemp : Product()
|
||||||
|
|
||||||
|
@Entity(tableName = "category", primaryKeys = ["repository_id", "package_name", "name"])
|
||||||
|
open class Category {
|
||||||
var repository_id: Long = 0
|
var repository_id: Long = 0
|
||||||
var package_name = ""
|
var package_name = ""
|
||||||
var name = ""
|
var name = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity(tableName = "temporary_category")
|
||||||
class Installed {
|
class CategoryTemp : Category()
|
||||||
|
|
||||||
|
@Entity(tableName = "memory_installed")
|
||||||
|
class Installed(pName: String = "") {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
var package_name = ""
|
var package_name = pName
|
||||||
|
|
||||||
var version = ""
|
var version = ""
|
||||||
var version_code = 0
|
var version_code = 0L
|
||||||
var signature = ""
|
var signature = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
@Entity(tableName = "memory_lock")
|
||||||
class Lock {
|
class Lock {
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
var package_name = ""
|
var package_name = ""
|
||||||
|
|
||||||
var version_code = 0
|
var version_code = 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
object Converters {
|
object Converters {
|
||||||
@ -87,11 +92,12 @@ object Converters {
|
|||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun toProduct(byteArray: ByteArray) = byteArray.jsonParse { Product.deserialize(it) }
|
fun toProduct(byteArray: ByteArray) =
|
||||||
|
byteArray.jsonParse { com.looker.droidify.entity.Product.deserialize(it) }
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun toByteArray(product: Product) = jsonGenerate(product::serialize)
|
fun toByteArray(product: com.looker.droidify.entity.Product) = jsonGenerate(product::serialize)
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -3,7 +3,7 @@ package com.looker.droidify.index
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.looker.droidify.content.Cache
|
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.Product
|
||||||
import com.looker.droidify.entity.Release
|
import com.looker.droidify.entity.Release
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
@ -59,29 +59,28 @@ object RepositoryUpdater {
|
|||||||
|
|
||||||
private val updaterLock = Any()
|
private val updaterLock = Any()
|
||||||
private val cleanupLock = Any()
|
private val cleanupLock = Any()
|
||||||
|
lateinit var db: DatabaseX
|
||||||
|
|
||||||
fun init() {
|
fun init(context: Context) {
|
||||||
|
db = DatabaseX.getInstance(context)
|
||||||
var lastDisabled = setOf<Long>()
|
var lastDisabled = setOf<Long>()
|
||||||
Observable.just(Unit)
|
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 {
|
.flatMapSingle {
|
||||||
RxUtils.querySingle {
|
RxUtils.querySingle {
|
||||||
Database.RepositoryAdapter.getAllDisabledDeleted(
|
db.repositoryDao.allDisabledDeleted
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.forEach { it ->
|
.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
|
val disabled = newDisabled - lastDisabled
|
||||||
lastDisabled = newDisabled
|
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()) {
|
if (disabled.isNotEmpty() || deleted.isNotEmpty()) {
|
||||||
val pairs = (disabled.asSequence().map { Pair(it, false) } +
|
val pairs = (disabled.asSequence().map { Pair(it, false) } +
|
||||||
deleted.asSequence().map { Pair(it, true) }).toSet()
|
deleted.asSequence().map { Pair(it, true) }).toSet()
|
||||||
synchronized(cleanupLock) { Database.RepositoryAdapter.cleanup(pairs) }
|
synchronized(cleanupLock) { db.cleanUp(pairs) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,12 +192,14 @@ object RepositoryUpdater {
|
|||||||
file: File, lastModified: String, entityTag: String, callback: (Stage, Long, Long?) -> Unit,
|
file: File, lastModified: String, entityTag: String, callback: (Stage, Long, Long?) -> Unit,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var rollback = true
|
var rollback = true
|
||||||
|
val db = DatabaseX.getInstance(context)
|
||||||
return synchronized(updaterLock) {
|
return synchronized(updaterLock) {
|
||||||
try {
|
try {
|
||||||
val jarFile = JarFile(file, true)
|
val jarFile = JarFile(file, true)
|
||||||
val indexEntry = jarFile.getEntry(indexType.contentName) as JarEntry
|
val indexEntry = jarFile.getEntry(indexType.contentName) as JarEntry
|
||||||
val total = indexEntry.size
|
val total = indexEntry.size
|
||||||
Database.UpdaterAdapter.createTemporaryTable()
|
db.productTempDao.emptyTable()
|
||||||
|
db.categoryTempDao.emptyTable()
|
||||||
val features = context.packageManager.systemAvailableFeatures
|
val features = context.packageManager.systemAvailableFeatures
|
||||||
.asSequence().map { it.name }.toSet() + setOf("android.hardware.touchscreen")
|
.asSequence().map { it.name }.toSet() + setOf("android.hardware.touchscreen")
|
||||||
|
|
||||||
@ -231,7 +232,7 @@ object RepositoryUpdater {
|
|||||||
}
|
}
|
||||||
products += transformProduct(product, features, unstable)
|
products += transformProduct(product, features, unstable)
|
||||||
if (products.size >= 50) {
|
if (products.size >= 50) {
|
||||||
Database.UpdaterAdapter.putTemporary(products)
|
db.productTempDao.putTemporary(products)
|
||||||
products.clear()
|
products.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +250,7 @@ object RepositoryUpdater {
|
|||||||
throw InterruptedException()
|
throw InterruptedException()
|
||||||
}
|
}
|
||||||
if (products.isNotEmpty()) {
|
if (products.isNotEmpty()) {
|
||||||
Database.UpdaterAdapter.putTemporary(products)
|
db.productTempDao.putTemporary(products)
|
||||||
products.clear()
|
products.clear()
|
||||||
}
|
}
|
||||||
Pair(changedRepository, certificateFromIndex)
|
Pair(changedRepository, certificateFromIndex)
|
||||||
@ -334,7 +335,7 @@ object RepositoryUpdater {
|
|||||||
progress.toLong(),
|
progress.toLong(),
|
||||||
totalCount.toLong()
|
totalCount.toLong()
|
||||||
)
|
)
|
||||||
Database.UpdaterAdapter.putTemporary(products
|
db.productTempDao.putTemporary(products
|
||||||
.map { transformProduct(it, features, unstable) })
|
.map { transformProduct(it, features, unstable) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -407,7 +408,7 @@ object RepositoryUpdater {
|
|||||||
}
|
}
|
||||||
callback(Stage.COMMIT, 0, null)
|
callback(Stage.COMMIT, 0, null)
|
||||||
synchronized(cleanupLock) {
|
synchronized(cleanupLock) {
|
||||||
Database.UpdaterAdapter.finishTemporary(
|
db.finishTemporary(
|
||||||
commitRepository,
|
commitRepository,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@ -423,7 +424,7 @@ object RepositoryUpdater {
|
|||||||
} finally {
|
} finally {
|
||||||
file.delete()
|
file.delete()
|
||||||
if (rollback) {
|
if (rollback) {
|
||||||
Database.UpdaterAdapter.finishTemporary(repository, false)
|
db.finishTemporary(repository, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import androidx.fragment.app.DialogFragment
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.databinding.EditRepositoryBinding
|
import com.looker.droidify.databinding.EditRepositoryBinding
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.network.Downloader
|
import com.looker.droidify.network.Downloader
|
||||||
@ -154,7 +153,7 @@ class EditRepositoryFragment() : ScreenFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val repository = repositoryId?.let(Database.RepositoryAdapter::get)
|
val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data }
|
||||||
if (repository == null) {
|
if (repository == null) {
|
||||||
val clipboardManager =
|
val clipboardManager =
|
||||||
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
@ -233,7 +232,7 @@ class EditRepositoryFragment() : ScreenFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val list = Database.RepositoryAdapter.getAll(null)
|
val list = screenActivity.db.repositoryDao.all.mapNotNull { it.data }
|
||||||
takenAddresses = list.asSequence().filter { it.id != repositoryId }
|
takenAddresses = list.asSequence().filter { it.id != repositoryId }
|
||||||
.flatMap { (it.mirrors + it.address).asSequence() }
|
.flatMap { (it.mirrors + it.address).asSequence() }
|
||||||
.map { it.withoutKnownPath }.toSet()
|
.map { it.withoutKnownPath }.toSet()
|
||||||
@ -449,10 +448,10 @@ class EditRepositoryFragment() : ScreenFragment() {
|
|||||||
MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager)
|
MessageDialog(MessageDialog.Message.CantEditSyncing).show(childFragmentManager)
|
||||||
invalidateState()
|
invalidateState()
|
||||||
} else {
|
} else {
|
||||||
val repository = repositoryId?.let(Database.RepositoryAdapter::get)
|
val repository = repositoryId?.let { screenActivity.db.repositoryDao.get(it)?.data }
|
||||||
?.edit(address, fingerprint, authentication)
|
?.edit(address, fingerprint, authentication)
|
||||||
?: Repository.newRepository(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) {
|
if (repositoryId == null && changedRepository.enabled) {
|
||||||
binder.sync(changedRepository)
|
binder.sync(changedRepository)
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ import com.google.android.material.card.MaterialCardView
|
|||||||
import com.google.android.material.imageview.ShapeableImageView
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
import com.google.android.material.textview.MaterialTextView
|
import com.google.android.material.textview.MaterialTextView
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.utility.extension.resources.clear
|
import com.looker.droidify.utility.extension.resources.clear
|
||||||
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
||||||
import com.looker.droidify.utility.extension.resources.inflate
|
import com.looker.droidify.utility.extension.resources.inflate
|
||||||
|
import com.looker.droidify.utility.getRepository
|
||||||
import com.looker.droidify.widget.CursorRecyclerAdapter
|
import com.looker.droidify.widget.CursorRecyclerAdapter
|
||||||
|
|
||||||
class RepositoriesAdapter(
|
class RepositoriesAdapter(
|
||||||
@ -45,7 +45,7 @@ class RepositoriesAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getRepository(position: Int): Repository {
|
private fun getRepository(position: Int): Repository {
|
||||||
return Database.RepositoryAdapter.transform(moveTo(position))
|
return moveTo(position).getRepository()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
|
@ -12,7 +12,6 @@ import androidx.appcompat.widget.LinearLayoutCompat
|
|||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.databinding.TitleTextItemBinding
|
import com.looker.droidify.databinding.TitleTextItemBinding
|
||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
import com.looker.droidify.service.SyncService
|
import com.looker.droidify.service.SyncService
|
||||||
@ -99,7 +98,7 @@ class RepositoryFragment() : ScreenFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRepositoryView() {
|
private fun updateRepositoryView() {
|
||||||
val repository = Database.RepositoryAdapter.get(repositoryId)
|
val repository = screenActivity.db.repositoryDao.get(repositoryId)?.data
|
||||||
val layout = layout!!
|
val layout = layout!!
|
||||||
layout.removeAllViews()
|
layout.removeAllViews()
|
||||||
if (repository == null) {
|
if (repository == null) {
|
||||||
@ -125,7 +124,7 @@ class RepositoryFragment() : ScreenFragment() {
|
|||||||
if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) {
|
if (repository.enabled && (repository.lastModified.isNotEmpty() || repository.entityTag.isNotEmpty())) {
|
||||||
layout.addTitleText(
|
layout.addTitleText(
|
||||||
R.string.number_of_applications,
|
R.string.number_of_applications,
|
||||||
Database.ProductAdapter.getCount(repository.id).toString()
|
screenActivity.db.productDao.countForRepository(repository.id).toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,6 +10,7 @@ import androidx.appcompat.widget.Toolbar
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.circularreveal.CircularRevealFrameLayout
|
import com.google.android.material.circularreveal.CircularRevealFrameLayout
|
||||||
|
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
|
||||||
@ -25,6 +26,9 @@ abstract class ScreenActivity : AppCompatActivity() {
|
|||||||
private const val STATE_FRAGMENT_STACK = "fragmentStack"
|
private const val STATE_FRAGMENT_STACK = "fragmentStack"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val db
|
||||||
|
get() = (application as MainApplication).db
|
||||||
|
|
||||||
sealed class SpecialIntent {
|
sealed class SpecialIntent {
|
||||||
object Updates : SpecialIntent()
|
object Updates : SpecialIntent()
|
||||||
class Install(val packageName: String?, val cacheFileName: String?) : SpecialIntent()
|
class Install(val packageName: String?, val cacheFileName: String?) : SpecialIntent()
|
||||||
|
@ -19,7 +19,7 @@ import androidx.viewpager2.widget.ViewPager2
|
|||||||
import coil.load
|
import coil.load
|
||||||
import com.google.android.material.imageview.ShapeableImageView
|
import com.google.android.material.imageview.ShapeableImageView
|
||||||
import com.looker.droidify.R
|
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.Product
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.graphics.PaddingDrawable
|
import com.looker.droidify.graphics.PaddingDrawable
|
||||||
@ -68,6 +68,7 @@ class ScreenshotsFragment() : DialogFragment() {
|
|||||||
|
|
||||||
val window = dialog.window
|
val window = dialog.window
|
||||||
val decorView = window?.decorView
|
val decorView = window?.decorView
|
||||||
|
val db = DatabaseX.getInstance(requireContext())
|
||||||
|
|
||||||
if (window != null) {
|
if (window != null) {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
@ -132,13 +133,17 @@ class ScreenshotsFragment() : DialogFragment() {
|
|||||||
|
|
||||||
var restored = false
|
var restored = false
|
||||||
productDisposable = Observable.just(Unit)
|
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())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } }
|
.flatMapSingle {
|
||||||
|
RxUtils.querySingle {
|
||||||
|
db.productDao.get(packageName).mapNotNull { it?.data }
|
||||||
|
}
|
||||||
|
}
|
||||||
.map { it ->
|
.map { it ->
|
||||||
Pair(
|
Pair(
|
||||||
it.find { it.repositoryId == repositoryId },
|
it.find { it.repositoryId == repositoryId },
|
||||||
Database.RepositoryAdapter.get(repositoryId)
|
db.repositoryDao.get(repositoryId)?.data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -19,7 +19,6 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||||||
import com.google.android.material.textview.MaterialTextView
|
import com.google.android.material.textview.MaterialTextView
|
||||||
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.Database
|
|
||||||
import com.looker.droidify.databinding.TabsToolbarBinding
|
import com.looker.droidify.databinding.TabsToolbarBinding
|
||||||
import com.looker.droidify.entity.ProductItem
|
import com.looker.droidify.entity.ProductItem
|
||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
@ -235,9 +234,9 @@ class TabsFragment : ScreenFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
categoriesDisposable = Observable.just(Unit)
|
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())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.CategoryAdapter.getAll(it) } }
|
.flatMapSingle { RxUtils.querySingle { screenActivity.db.categoryDao.allNames } }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
setSectionsAndUpdate(
|
setSectionsAndUpdate(
|
||||||
@ -246,9 +245,9 @@ class TabsFragment : ScreenFragment() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
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 { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { it ->
|
.subscribe { it ->
|
||||||
setSectionsAndUpdate(null, it.asSequence().filter { it.enabled }
|
setSectionsAndUpdate(null, it.asSequence().filter { it.enabled }
|
||||||
|
@ -9,10 +9,7 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.looker.droidify.BuildConfig
|
import com.looker.droidify.*
|
||||||
import com.looker.droidify.Common
|
|
||||||
import com.looker.droidify.MainActivity
|
|
||||||
import com.looker.droidify.R
|
|
||||||
import com.looker.droidify.content.Cache
|
import com.looker.droidify.content.Cache
|
||||||
import com.looker.droidify.entity.Release
|
import com.looker.droidify.entity.Release
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
@ -121,7 +118,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
} else {
|
} else {
|
||||||
cancelTasks(packageName)
|
cancelTasks(packageName)
|
||||||
cancelCurrentTask(packageName)
|
cancelCurrentTask(packageName)
|
||||||
notificationManager.cancel(task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING)
|
notificationManager.cancel(task.notificationTag, NOTIFICATION_ID_DOWNLOADING)
|
||||||
tasks += task
|
tasks += task
|
||||||
if (currentTask == null) {
|
if (currentTask == null) {
|
||||||
handleDownload()
|
handleDownload()
|
||||||
@ -146,7 +143,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
if (Android.sdk(26)) {
|
if (Android.sdk(26)) {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
Common.NOTIFICATION_CHANNEL_DOWNLOADING,
|
NOTIFICATION_CHANNEL_DOWNLOADING,
|
||||||
getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW
|
getString(R.string.downloading), NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
.apply { setShowBadge(false) }
|
.apply { setShowBadge(false) }
|
||||||
@ -209,9 +206,9 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
private fun showNotificationError(task: Task, errorType: ErrorType) {
|
private fun showNotificationError(task: Task, errorType: ErrorType) {
|
||||||
notificationManager.notify(task.notificationTag,
|
notificationManager.notify(task.notificationTag,
|
||||||
Common.NOTIFICATION_ID_DOWNLOADING,
|
NOTIFICATION_ID_DOWNLOADING,
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
.setColor(
|
.setColor(
|
||||||
@ -276,8 +273,8 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
private fun showNotificationInstall(task: Task) {
|
private fun showNotificationInstall(task: Task) {
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
task.notificationTag, Common.NOTIFICATION_ID_DOWNLOADING, NotificationCompat
|
task.notificationTag, NOTIFICATION_ID_DOWNLOADING, NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
.setColor(
|
.setColor(
|
||||||
@ -367,7 +364,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
|
|
||||||
private val stateNotificationBuilder by lazy {
|
private val stateNotificationBuilder by lazy {
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
.Builder(this, NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -389,7 +386,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
|
|||||||
private fun publishForegroundState(force: Boolean, state: State) {
|
private fun publishForegroundState(force: Boolean, state: State) {
|
||||||
if (force || currentTask != null) {
|
if (force || currentTask != null) {
|
||||||
currentTask = currentTask?.copy(lastState = state)
|
currentTask = currentTask?.copy(lastState = state)
|
||||||
startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||||
when (state) {
|
when (state) {
|
||||||
is State.Connecting -> {
|
is State.Connecting -> {
|
||||||
setContentTitle(getString(R.string.downloading_FORMAT, state.name))
|
setContentTitle(getString(R.string.downloading_FORMAT, state.name))
|
||||||
|
@ -13,12 +13,9 @@ import android.text.style.ForegroundColorSpan
|
|||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.looker.droidify.BuildConfig
|
import com.looker.droidify.*
|
||||||
import com.looker.droidify.Common
|
|
||||||
import com.looker.droidify.MainActivity
|
|
||||||
import com.looker.droidify.R
|
|
||||||
import com.looker.droidify.content.Preferences
|
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.ProductItem
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.index.RepositoryUpdater
|
import com.looker.droidify.index.RepositoryUpdater
|
||||||
@ -28,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.android.notificationManager
|
||||||
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
||||||
import com.looker.droidify.utility.extension.text.formatSize
|
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.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
@ -99,7 +97,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sync(request: SyncRequest) {
|
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()
|
.asSequence().filter { it.enabled }.map { it.id }.toList()
|
||||||
sync(ids, request)
|
sync(ids, request)
|
||||||
}
|
}
|
||||||
@ -120,12 +118,12 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
fun setUpdateNotificationBlocker(fragment: Fragment?) {
|
fun setUpdateNotificationBlocker(fragment: Fragment?) {
|
||||||
updateNotificationBlockerFragment = fragment?.let(::WeakReference)
|
updateNotificationBlockerFragment = fragment?.let(::WeakReference)
|
||||||
if (fragment != null) {
|
if (fragment != null) {
|
||||||
notificationManager.cancel(Common.NOTIFICATION_ID_UPDATES)
|
notificationManager.cancel(NOTIFICATION_ID_UPDATES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setEnabled(repository: Repository, enabled: Boolean): Boolean {
|
fun setEnabled(repository: Repository, enabled: Boolean): Boolean {
|
||||||
Database.RepositoryAdapter.put(repository.enable(enabled))
|
db.repositoryDao.put(repository.enable(enabled))
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (repository.id != currentTask?.task?.repositoryId && !tasks.any { it.repositoryId == repository.id }) {
|
if (repository.id != currentTask?.task?.repositoryId && !tasks.any { it.repositoryId == repository.id }) {
|
||||||
tasks += Task(repository.id, true)
|
tasks += Task(repository.id, true)
|
||||||
@ -144,10 +142,10 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteRepository(repositoryId: Long): Boolean {
|
fun deleteRepository(repositoryId: Long): Boolean {
|
||||||
val repository = Database.RepositoryAdapter.get(repositoryId)
|
val repository = db.repositoryDao.get(repositoryId)?.data
|
||||||
return repository != null && run {
|
return repository != null && run {
|
||||||
setEnabled(repository, false)
|
setEnabled(repository, false)
|
||||||
Database.RepositoryAdapter.markAsDeleted(repository.id)
|
db.repositoryDao.markAsDeleted(repository.id)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,19 +153,21 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
|
|
||||||
private val binder = Binder()
|
private val binder = Binder()
|
||||||
override fun onBind(intent: Intent): Binder = binder
|
override fun onBind(intent: Intent): Binder = binder
|
||||||
|
lateinit var db: DatabaseX
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
db = DatabaseX.getInstance(applicationContext)
|
||||||
if (Android.sdk(26)) {
|
if (Android.sdk(26)) {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
Common.NOTIFICATION_CHANNEL_SYNCING,
|
NOTIFICATION_CHANNEL_SYNCING,
|
||||||
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
getString(R.string.syncing), NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
.apply { setShowBadge(false) }
|
.apply { setShowBadge(false) }
|
||||||
.let(notificationManager::createNotificationChannel)
|
.let(notificationManager::createNotificationChannel)
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
Common.NOTIFICATION_CHANNEL_UPDATES,
|
NOTIFICATION_CHANNEL_UPDATES,
|
||||||
getString(R.string.updates), NotificationManager.IMPORTANCE_LOW
|
getString(R.string.updates), NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
.let(notificationManager::createNotificationChannel)
|
.let(notificationManager::createNotificationChannel)
|
||||||
@ -210,8 +210,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
|
|
||||||
private fun showNotificationError(repository: Repository, exception: Exception) {
|
private fun showNotificationError(repository: Repository, exception: Exception) {
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
"repository-${repository.id}", Common.NOTIFICATION_ID_SYNCING, NotificationCompat
|
"repository-${repository.id}", NOTIFICATION_ID_SYNCING, NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING)
|
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -237,7 +237,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
|
|
||||||
private val stateNotificationBuilder by lazy {
|
private val stateNotificationBuilder by lazy {
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_SYNCING)
|
.Builder(this, NOTIFICATION_CHANNEL_SYNCING)
|
||||||
.setSmallIcon(R.drawable.ic_sync)
|
.setSmallIcon(R.drawable.ic_sync)
|
||||||
.setColor(
|
.setColor(
|
||||||
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
@ -260,7 +260,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
if (force || currentTask?.lastState != state) {
|
if (force || currentTask?.lastState != state) {
|
||||||
currentTask = currentTask?.copy(lastState = state)
|
currentTask = currentTask?.copy(lastState = state)
|
||||||
if (started == Started.MANUAL) {
|
if (started == Started.MANUAL) {
|
||||||
startForeground(Common.NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
startForeground(NOTIFICATION_ID_SYNCING, stateNotificationBuilder.apply {
|
||||||
when (state) {
|
when (state) {
|
||||||
is State.Connecting -> {
|
is State.Connecting -> {
|
||||||
setContentTitle(getString(R.string.syncing_FORMAT, state.name))
|
setContentTitle(getString(R.string.syncing_FORMAT, state.name))
|
||||||
@ -330,7 +330,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
if (currentTask == null) {
|
if (currentTask == null) {
|
||||||
if (tasks.isNotEmpty()) {
|
if (tasks.isNotEmpty()) {
|
||||||
val task = tasks.removeAt(0)
|
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) {
|
if (repository != null && repository.enabled) {
|
||||||
val lastStarted = started
|
val lastStarted = started
|
||||||
val newStarted =
|
val newStarted =
|
||||||
@ -376,7 +376,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
if (hasUpdates && Preferences[Preferences.Key.UpdateNotify]) {
|
if (hasUpdates && Preferences[Preferences.Key.UpdateNotify]) {
|
||||||
val disposable = RxUtils
|
val disposable = RxUtils
|
||||||
.querySingle { it ->
|
.querySingle { it ->
|
||||||
Database.ProductAdapter
|
db.productDao
|
||||||
.query(
|
.query(
|
||||||
installed = true,
|
installed = true,
|
||||||
updates = true,
|
updates = true,
|
||||||
@ -386,8 +386,7 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
signal = it
|
signal = it
|
||||||
)
|
)
|
||||||
.use {
|
.use {
|
||||||
it.asSequence().map(Database.ProductAdapter::transformItem)
|
it.asSequence().map { it.getProductItem() }.toList()
|
||||||
.toList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@ -419,8 +418,8 @@ class SyncService : ConnectionService<SyncService.Binder>() {
|
|||||||
val maxUpdates = 5
|
val maxUpdates = 5
|
||||||
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
|
fun <T> T.applyHack(callback: T.() -> Unit): T = apply(callback)
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
Common.NOTIFICATION_ID_UPDATES, NotificationCompat
|
NOTIFICATION_ID_UPDATES, NotificationCompat
|
||||||
.Builder(this, Common.NOTIFICATION_CHANNEL_UPDATES)
|
.Builder(this, NOTIFICATION_CHANNEL_UPDATES)
|
||||||
.setSmallIcon(R.drawable.ic_new_releases)
|
.setSmallIcon(R.drawable.ic_new_releases)
|
||||||
.setContentTitle(getString(R.string.new_updates_available))
|
.setContentTitle(getString(R.string.new_updates_available))
|
||||||
.setContentText(
|
.setContentText(
|
||||||
|
@ -16,13 +16,13 @@ import com.google.android.material.progressindicator.CircularProgressIndicator
|
|||||||
import com.google.android.material.textview.MaterialTextView
|
import com.google.android.material.textview.MaterialTextView
|
||||||
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.Database
|
|
||||||
import com.looker.droidify.entity.ProductItem
|
import com.looker.droidify.entity.ProductItem
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.network.CoilDownloader
|
import com.looker.droidify.network.CoilDownloader
|
||||||
import com.looker.droidify.utility.Utils
|
import com.looker.droidify.utility.Utils
|
||||||
import com.looker.droidify.utility.extension.resources.*
|
import com.looker.droidify.utility.extension.resources.*
|
||||||
import com.looker.droidify.utility.extension.text.nullIfEmpty
|
import com.looker.droidify.utility.extension.text.nullIfEmpty
|
||||||
|
import com.looker.droidify.utility.getProductItem
|
||||||
import com.looker.droidify.widget.CursorRecyclerAdapter
|
import com.looker.droidify.widget.CursorRecyclerAdapter
|
||||||
|
|
||||||
class AppListAdapter(private val onClick: (ProductItem) -> Unit) :
|
class AppListAdapter(private val onClick: (ProductItem) -> Unit) :
|
||||||
@ -113,7 +113,7 @@ class AppListAdapter(private val onClick: (ProductItem) -> Unit) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getProductItem(position: Int): ProductItem {
|
private fun getProductItem(position: Int): ProductItem {
|
||||||
return Database.ProductAdapter.transformItem(moveTo(position))
|
return moveTo(position).getProductItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
|
@ -17,7 +17,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.looker.droidify.R
|
import com.looker.droidify.R
|
||||||
import com.looker.droidify.content.ProductPreferences
|
import com.looker.droidify.content.ProductPreferences
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.entity.*
|
import com.looker.droidify.entity.*
|
||||||
import com.looker.droidify.installer.AppInstaller
|
import com.looker.droidify.installer.AppInstaller
|
||||||
import com.looker.droidify.screen.MessageDialog
|
import com.looker.droidify.screen.MessageDialog
|
||||||
@ -32,6 +31,7 @@ import com.looker.droidify.utility.Utils.rootInstallerEnabled
|
|||||||
import com.looker.droidify.utility.Utils.startUpdate
|
import com.looker.droidify.utility.Utils.startUpdate
|
||||||
import com.looker.droidify.utility.extension.android.*
|
import com.looker.droidify.utility.extension.android.*
|
||||||
import com.looker.droidify.utility.extension.text.trimAfter
|
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.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import io.reactivex.rxjava3.disposables.Disposable
|
import io.reactivex.rxjava3.disposables.Disposable
|
||||||
@ -129,12 +129,16 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
|
|||||||
|
|
||||||
var first = true
|
var first = true
|
||||||
productDisposable = Observable.just(Unit)
|
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())
|
.observeOn(Schedulers.io())
|
||||||
.flatMapSingle { RxUtils.querySingle { Database.ProductAdapter.get(packageName, it) } }
|
.flatMapSingle {
|
||||||
|
RxUtils.querySingle {
|
||||||
|
screenActivity.db.productDao.get(packageName).mapNotNull { it?.data }
|
||||||
|
}
|
||||||
|
}
|
||||||
.flatMapSingle { products ->
|
.flatMapSingle { products ->
|
||||||
RxUtils
|
RxUtils
|
||||||
.querySingle { Database.RepositoryAdapter.getAll(it) }
|
.querySingle { screenActivity.db.repositoryDao.all.mapNotNull { it.data } }
|
||||||
.map { it ->
|
.map { it ->
|
||||||
it.asSequence().map { Pair(it.id, it) }.toMap()
|
it.asSequence().map { Pair(it.id, it) }.toMap()
|
||||||
.let {
|
.let {
|
||||||
@ -151,7 +155,7 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
.flatMapSingle { products ->
|
.flatMapSingle { products ->
|
||||||
RxUtils
|
RxUtils
|
||||||
.querySingle { Nullable(Database.InstalledAdapter.get(packageName, it)) }
|
.querySingle { Nullable(screenActivity.db.installedDao.get(packageName).getInstalledItem()) }
|
||||||
.map { Pair(products, it) }
|
.map { Pair(products, it) }
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -13,7 +13,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
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.entity.ProductItem
|
import com.looker.droidify.entity.ProductItem
|
||||||
import com.looker.droidify.screen.BaseFragment
|
import com.looker.droidify.screen.BaseFragment
|
||||||
import com.looker.droidify.ui.adapters.AppListAdapter
|
import com.looker.droidify.ui.adapters.AppListAdapter
|
||||||
@ -78,10 +77,10 @@ class AppListFragment() : BaseFragment(), CursorOwner.Callback {
|
|||||||
|
|
||||||
screenActivity.cursorOwner.attach(this, viewModel.request(source))
|
screenActivity.cursorOwner.attach(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 { screenActivity.db.repositoryDao.all.mapNotNull { it.data } } }
|
||||||
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
|
.map { it.asSequence().map { Pair(it.id, it) }.toMap() }
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { (recyclerView?.adapter as? AppListAdapter)?.repositories = it }
|
.subscribe { (recyclerView?.adapter as? AppListAdapter)?.repositories = it }
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,20 @@ import android.content.pm.Signature
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.looker.droidify.BuildConfig
|
import com.looker.droidify.*
|
||||||
import com.looker.droidify.Common.PREFS_LANGUAGE_DEFAULT
|
|
||||||
import com.looker.droidify.R
|
|
||||||
import com.looker.droidify.content.Preferences
|
import com.looker.droidify.content.Preferences
|
||||||
import com.looker.droidify.entity.InstalledItem
|
import com.looker.droidify.entity.InstalledItem
|
||||||
import com.looker.droidify.entity.Product
|
import com.looker.droidify.entity.Product
|
||||||
|
import com.looker.droidify.entity.ProductItem
|
||||||
import com.looker.droidify.entity.Repository
|
import com.looker.droidify.entity.Repository
|
||||||
import com.looker.droidify.service.Connection
|
import com.looker.droidify.service.Connection
|
||||||
import com.looker.droidify.service.DownloadService
|
import com.looker.droidify.service.DownloadService
|
||||||
import com.looker.droidify.utility.extension.android.Android
|
import com.looker.droidify.utility.extension.android.Android
|
||||||
import com.looker.droidify.utility.extension.android.singleSignature
|
import com.looker.droidify.utility.extension.android.singleSignature
|
||||||
import com.looker.droidify.utility.extension.android.versionCodeCompat
|
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.getColorFromAttr
|
||||||
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
import com.looker.droidify.utility.extension.resources.getDrawableCompat
|
||||||
import com.looker.droidify.utility.extension.text.hex
|
import com.looker.droidify.utility.extension.text.hex
|
||||||
@ -167,3 +169,51 @@ object Utils {
|
|||||||
else -> Locale(localeCode)
|
else -> Locale(localeCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Cursor.getInstalledItem(): InstalledItem = InstalledItem(
|
||||||
|
getString(getColumnIndex(ROW_PACKAGE_NAME)),
|
||||||
|
getString(getColumnIndex(ROW_VERSION)),
|
||||||
|
getLong(getColumnIndex(ROW_VERSION_CODE)),
|
||||||
|
getString(getColumnIndex(ROW_SIGNATURE))
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user