mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-08-07 04:53:37 -04:00
[db] New AppOverviewItems AppDao methods
This commit is contained in:
2 files changed
+256
-32
No files matched your search
@@ -1,16 +1,20 @@
|
|||||||
package org.fdroid.database
|
package org.fdroid.database
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.fdroid.LocaleChooser.getBestLocale
|
import org.fdroid.LocaleChooser.getBestLocale
|
||||||
import org.fdroid.database.TestUtils.getOrAwaitValue
|
import org.fdroid.database.TestUtils.getOrAwaitValue
|
||||||
import org.fdroid.database.TestUtils.getOrFail
|
import org.fdroid.database.TestUtils.getOrFail
|
||||||
import org.fdroid.index.v2.MetadataV2
|
import org.fdroid.index.v2.MetadataV2
|
||||||
|
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
|
||||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||||
|
import org.fdroid.test.TestUtils.getRandomString
|
||||||
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
|
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@@ -19,13 +23,13 @@ import kotlin.test.assertTrue
|
|||||||
internal class AppOverviewItemsTest : AppTest() {
|
internal class AppOverviewItemsTest : AppTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAntiFeatures() {
|
fun testAntiFeatures() = runBlocking {
|
||||||
// insert one apps with without version
|
// insert one app with without version
|
||||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
appDao.insert(repoId, packageName, app1, locales)
|
appDao.insert(repoId, packageName, app1, locales)
|
||||||
|
|
||||||
// without version, anti-features are empty
|
// without version, anti-features are empty
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertNull(apps[0].antiFeatures)
|
assertNull(apps[0].antiFeatures)
|
||||||
}
|
}
|
||||||
@@ -33,7 +37,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
// with one version, the app has those anti-features
|
// with one version, the app has those anti-features
|
||||||
val version = getRandomPackageVersionV2(versionCode = 42)
|
val version = getRandomPackageVersionV2(versionCode = 42)
|
||||||
versionDao.insert(repoId, packageName, "1", version, true)
|
versionDao.insert(repoId, packageName, "1", version, true)
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(version.antiFeatures, apps[0].antiFeatures)
|
assertEquals(version.antiFeatures, apps[0].antiFeatures)
|
||||||
}
|
}
|
||||||
@@ -41,7 +45,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
// with two versions, the app has the anti-features of the highest version
|
// with two versions, the app has the anti-features of the highest version
|
||||||
val version2 = getRandomPackageVersionV2(versionCode = 23)
|
val version2 = getRandomPackageVersionV2(versionCode = 23)
|
||||||
versionDao.insert(repoId, packageName, "2", version2, true)
|
versionDao.insert(repoId, packageName, "2", version2, true)
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(version.antiFeatures, apps[0].antiFeatures)
|
assertEquals(version.antiFeatures, apps[0].antiFeatures)
|
||||||
}
|
}
|
||||||
@@ -49,20 +53,20 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
// with three versions, the app has the anti-features of the highest version
|
// with three versions, the app has the anti-features of the highest version
|
||||||
val version3 = getRandomPackageVersionV2(versionCode = 1337)
|
val version3 = getRandomPackageVersionV2(versionCode = 1337)
|
||||||
versionDao.insert(repoId, packageName, "3", version3, true)
|
versionDao.insert(repoId, packageName, "3", version3, true)
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(version3.antiFeatures, apps[0].antiFeatures)
|
assertEquals(version3.antiFeatures, apps[0].antiFeatures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIcons() {
|
fun testIcons() = runBlocking {
|
||||||
// insert one app
|
// insert one app
|
||||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
appDao.insert(repoId, packageName, app1, locales)
|
appDao.insert(repoId, packageName, app1, locales)
|
||||||
|
|
||||||
// icon is returned correctly
|
// icon is returned correctly
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||||
}
|
}
|
||||||
@@ -72,14 +76,14 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
appDao.insert(repoId2, packageName, app2, locales)
|
appDao.insert(repoId2, packageName, app2, locales)
|
||||||
|
|
||||||
// app is still returned as before
|
// app is still returned as before
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||||
}
|
}
|
||||||
|
|
||||||
// after preferring second repo, icon is returned from app in second repo
|
// after preferring second repo, icon is returned from app in second repo
|
||||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app2.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
assertEquals(app2.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||||
}
|
}
|
||||||
@@ -99,17 +103,18 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIncompatibleFlag() {
|
fun testIncompatibleFlag() = runBlocking {
|
||||||
// insert two apps
|
// insert two apps
|
||||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
appDao.insert(repoId, packageName1, app1, locales)
|
appDao.insert(repoId, packageName1, app1, locales)
|
||||||
appDao.insert(repoId, packageName2, app2, locales)
|
appDao.insert(repoId, packageName2, app2, locales)
|
||||||
|
|
||||||
// both apps are not compatible
|
// both apps are not compatible
|
||||||
appDao.getAppOverviewItems().getOrFail().also {
|
getItems().forEach { apps ->
|
||||||
assertEquals(2, it.size)
|
assertEquals(2, apps.size)
|
||||||
}.forEach {
|
apps.forEach {
|
||||||
assertFalse(it.isCompatible)
|
assertFalse(it.isCompatible)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// both apps, in the same category, are not compatible
|
// both apps, in the same category, are not compatible
|
||||||
appDao.getAppOverviewItems("A").getOrFail().also {
|
appDao.getAppOverviewItems("A").getOrFail().also {
|
||||||
@@ -128,10 +133,10 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
appDao.updateCompatibility(repoId)
|
appDao.updateCompatibility(repoId)
|
||||||
|
|
||||||
// now only one is not compatible
|
// now only one is not compatible
|
||||||
appDao.getAppOverviewItems().getOrFail().also {
|
getItems().forEach { apps ->
|
||||||
assertEquals(2, it.size)
|
assertEquals(2, apps.size)
|
||||||
assertFalse(it[0].isCompatible)
|
assertFalse(apps[0].isCompatible)
|
||||||
assertTrue(it[1].isCompatible)
|
assertTrue(apps[1].isCompatible)
|
||||||
}
|
}
|
||||||
appDao.getAppOverviewItems("A").getOrFail().also {
|
appDao.getAppOverviewItems("A").getOrFail().also {
|
||||||
assertEquals(2, it.size)
|
assertEquals(2, it.size)
|
||||||
@@ -143,14 +148,14 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetByRepoWeight() {
|
fun testGetByRepoWeight() = runBlocking {
|
||||||
// insert one app with one version
|
// insert one app with one version
|
||||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
appDao.insert(repoId, packageName, app1, locales)
|
appDao.insert(repoId, packageName, app1, locales)
|
||||||
versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(2), true)
|
versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(2), true)
|
||||||
|
|
||||||
// app is returned correctly
|
// app is returned correctly
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app1, apps[0])
|
assertEquals(app1, apps[0])
|
||||||
}
|
}
|
||||||
@@ -160,21 +165,21 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
appDao.insert(repoId2, packageName, app2, locales)
|
appDao.insert(repoId2, packageName, app2, locales)
|
||||||
|
|
||||||
// app is still returned as before, new repo doesn't override old one
|
// app is still returned as before, new repo doesn't override old one
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app1, apps[0])
|
assertEquals(app1, apps[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// now second app from second repo is returned after preferring it explicitly
|
// now second app from second repo is returned after preferring it explicitly
|
||||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app2, apps[0])
|
assertEquals(app2, apps[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetByRepoPref() {
|
fun testGetByRepoPref() = runBlocking {
|
||||||
// insert same app into three repos (repoId1 has highest weight)
|
// insert same app into three repos (repoId1 has highest weight)
|
||||||
val repoId1 = repoDao.insertOrReplace(getRandomRepo())
|
val repoId1 = repoDao.insertOrReplace(getRandomRepo())
|
||||||
val repoId3 = repoDao.insertOrReplace(getRandomRepo())
|
val repoId3 = repoDao.insertOrReplace(getRandomRepo())
|
||||||
@@ -184,7 +189,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
appDao.insert(repoId3, packageName, app3, locales)
|
appDao.insert(repoId3, packageName, app3, locales)
|
||||||
|
|
||||||
// app is returned correctly from repo1
|
// app is returned correctly from repo1
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app1, apps[0])
|
assertEquals(app1, apps[0])
|
||||||
}
|
}
|
||||||
@@ -195,7 +200,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
|
|
||||||
// prefer repo3 for this app
|
// prefer repo3 for this app
|
||||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
|
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app3, apps[0])
|
assertEquals(app3, apps[0])
|
||||||
}
|
}
|
||||||
@@ -206,7 +211,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
|
|
||||||
// prefer repo2 for this app
|
// prefer repo2 for this app
|
||||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app2, apps[0])
|
assertEquals(app2, apps[0])
|
||||||
}
|
}
|
||||||
@@ -220,7 +225,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
|
|
||||||
// prefer repo1 for this app
|
// prefer repo1 for this app
|
||||||
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
|
appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
|
||||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
getItems().forEach { apps ->
|
||||||
assertEquals(1, apps.size)
|
assertEquals(1, apps.size)
|
||||||
assertEquals(app1, apps[0])
|
assertEquals(app1, apps[0])
|
||||||
}
|
}
|
||||||
@@ -341,7 +346,7 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnlyFromEnabledRepos() {
|
fun testOnlyFromEnabledRepos() = runBlocking {
|
||||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
appDao.insert(repoId, packageName1, app1, locales)
|
appDao.insert(repoId, packageName1, app1, locales)
|
||||||
appDao.insert(repoId, packageName2, app2, locales)
|
appDao.insert(repoId, packageName2, app2, locales)
|
||||||
@@ -349,18 +354,24 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
appDao.insert(repoId2, packageName3, app3, locales)
|
appDao.insert(repoId2, packageName3, app3, locales)
|
||||||
|
|
||||||
// 3 apps from 2 repos
|
// 3 apps from 2 repos
|
||||||
assertEquals(3, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
getItems().forEach { apps ->
|
||||||
|
assertEquals(3, apps.size)
|
||||||
|
}
|
||||||
assertEquals(3, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
assertEquals(3, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||||
|
|
||||||
// only 1 app after disabling first repo
|
// only 1 app after disabling first repo
|
||||||
repoDao.setRepositoryEnabled(repoId, false)
|
repoDao.setRepositoryEnabled(repoId, false)
|
||||||
assertEquals(1, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
getItems().forEach { apps ->
|
||||||
|
assertEquals(1, apps.size)
|
||||||
|
}
|
||||||
assertEquals(1, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
assertEquals(1, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||||
assertEquals(1, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
|
assertEquals(1, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
|
||||||
|
|
||||||
// no more apps after disabling all repos
|
// no more apps after disabling all repos
|
||||||
repoDao.setRepositoryEnabled(repoId2, false)
|
repoDao.setRepositoryEnabled(repoId2, false)
|
||||||
assertEquals(0, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
getItems().forEach { apps ->
|
||||||
|
assertEquals(0, apps.size)
|
||||||
|
}
|
||||||
assertEquals(0, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
assertEquals(0, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||||
assertEquals(0, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
|
assertEquals(0, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
|
||||||
}
|
}
|
||||||
@@ -405,6 +416,65 @@ internal class AppOverviewItemsTest : AppTest() {
|
|||||||
assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
|
assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testByAuthor() = runBlocking {
|
||||||
|
val author = getRandomString()
|
||||||
|
val packageName = getRandomString()
|
||||||
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
|
appDao.insert(repoId, packageName, getRandomMetadataV2(author), locales)
|
||||||
|
|
||||||
|
// author has only one app
|
||||||
|
assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
|
||||||
|
assertEquals(0, appDao.getAppsByAuthor("foo bar").size)
|
||||||
|
appDao.getAppsByAuthor(author).let { apps ->
|
||||||
|
assertEquals(1, apps.size)
|
||||||
|
assertEquals(packageName, apps[0].packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now add 49 more apps
|
||||||
|
(1 until 50).forEach { _ ->
|
||||||
|
appDao.insert(repoId, getRandomString(), getRandomMetadataV2(author), locales)
|
||||||
|
}
|
||||||
|
assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
|
||||||
|
assertEquals(50, appDao.getAppsByAuthor(author).size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testByCategory() = runBlocking {
|
||||||
|
// insert three apps
|
||||||
|
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||||
|
appDao.insert(repoId, packageName1, app1, locales)
|
||||||
|
appDao.insert(repoId, packageName2, app2, locales)
|
||||||
|
appDao.insert(repoId, packageName3, app3, locales)
|
||||||
|
|
||||||
|
// only two apps are in category B
|
||||||
|
appDao.getAppsByCategory("B").let { apps ->
|
||||||
|
assertEquals(2, apps.size)
|
||||||
|
assertNotEquals(packageName2, apps[0].packageName)
|
||||||
|
assertNotEquals(packageName2, apps[1].packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// no app is in category C
|
||||||
|
assertEquals(0, appDao.getAppsByCategory("C").size)
|
||||||
|
|
||||||
|
// we'll add app1 as a variant of app2, but its repo has lower weight, so no effect
|
||||||
|
val repoId2 = repoDao.insertOrReplace(getRandomRepo())
|
||||||
|
appDao.insert(repoId2, packageName2, app1, locales)
|
||||||
|
appDao.getAppsByCategory("B").let { apps ->
|
||||||
|
assertEquals(2, apps.size)
|
||||||
|
assertNotEquals(packageName2, apps[0].packageName)
|
||||||
|
assertNotEquals(packageName2, apps[1].packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getItems(): List<List<AppOverviewItem>> {
|
||||||
|
return listOf(
|
||||||
|
appDao.getAppOverviewItems().getOrFail(),
|
||||||
|
// manually sort the second list, so both results are comparable
|
||||||
|
appDao.getAllApps().sortedByDescending { it.lastUpdated },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun assertEquals(expected: MetadataV2, actual: AppOverviewItem?) {
|
private fun assertEquals(expected: MetadataV2, actual: AppOverviewItem?) {
|
||||||
assertNotNull(actual)
|
assertNotNull(actual)
|
||||||
assertEquals(expected.added, actual.added)
|
assertEquals(expected.added, actual.added)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import androidx.room.RoomRawQuery
|
|||||||
import androidx.room.RoomWarnings.Companion.QUERY_MISMATCH
|
import androidx.room.RoomWarnings.Companion.QUERY_MISMATCH
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
|
import androidx.sqlite.SQLiteStatement
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -34,6 +36,7 @@ import org.fdroid.index.v2.LocalizedFileListV2
|
|||||||
import org.fdroid.index.v2.LocalizedFileV2
|
import org.fdroid.index.v2.LocalizedFileV2
|
||||||
import org.fdroid.index.v2.MetadataV2
|
import org.fdroid.index.v2.MetadataV2
|
||||||
import org.fdroid.index.v2.ReflectionDiffer.applyDiff
|
import org.fdroid.index.v2.ReflectionDiffer.applyDiff
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
public interface AppDao {
|
public interface AppDao {
|
||||||
/**
|
/**
|
||||||
@@ -83,16 +86,62 @@ public interface AppDao {
|
|||||||
* Apps without name, icon or summary are at the end (or excluded if limit is too small).
|
* Apps without name, icon or summary are at the end (or excluded if limit is too small).
|
||||||
* Includes anti-features from the version with the highest version code.
|
* Includes anti-features from the version with the highest version code.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use getNewAppsFlow and getRecentlyUpdatedAppsFlow instead")
|
||||||
public fun getAppOverviewItems(limit: Int = 200): LiveData<List<AppOverviewItem>>
|
public fun getAppOverviewItems(limit: Int = 200): LiveData<List<AppOverviewItem>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a limited number of apps with limited data within the given [category].
|
* Returns a limited number of apps with limited data within the given [category].
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use getAppsByCategory instead")
|
||||||
public fun getAppOverviewItems(
|
public fun getAppOverviewItems(
|
||||||
category: String,
|
category: String,
|
||||||
limit: Int = 50,
|
limit: Int = 50,
|
||||||
): LiveData<List<AppOverviewItem>>
|
): LiveData<List<AppOverviewItem>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all apps from the database.
|
||||||
|
*/
|
||||||
|
public suspend fun getAllApps(): List<AppOverviewItem>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all apps whose author is set exactly to [authorName].
|
||||||
|
*/
|
||||||
|
public suspend fun getAppsByAuthor(authorName: String): List<AppOverviewItem>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all apps that are in the category with [categoryId].
|
||||||
|
*/
|
||||||
|
public suspend fun getAppsByCategory(categoryId: String): List<AppOverviewItem>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns apps that are new. This means that they were added and last updated at the same time.
|
||||||
|
* @param maxAgeInDays the number of days that is still considered "new".
|
||||||
|
* Apps older than this won't be returned.
|
||||||
|
*/
|
||||||
|
public suspend fun getNewApps(maxAgeInDays: Long = 14): List<AppOverviewItem>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get apps that were recently updated.
|
||||||
|
* This excludes apps returned by [getNewApps].
|
||||||
|
* @param limit only return that many apps and not more.
|
||||||
|
*/
|
||||||
|
public suspend fun getRecentlyUpdatedApps(limit: Int = 200): List<AppOverviewItem>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all apps from the repository identified by [repoId].
|
||||||
|
*/
|
||||||
|
public suspend fun getAppsByRepository(repoId: Long): List<AppOverviewItem>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as [getNewApps], but returns an observable [Flow].
|
||||||
|
*/
|
||||||
|
public fun getNewAppsFlow(maxAgeInDays: Long = 14): Flow<List<AppOverviewItem>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as [getRecentlyUpdatedApps], but returns an observable [Flow].
|
||||||
|
*/
|
||||||
|
public fun getRecentlyUpdatedAppsFlow(limit: Int = 200): Flow<List<AppOverviewItem>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of all [AppListItem] sorted by the given [sortOrder],
|
* Returns a list of all [AppListItem] sorted by the given [sortOrder],
|
||||||
* or a subset of [AppListItem]s filtered by the given [searchQuery] if it is non-null.
|
* or a subset of [AppListItem]s filtered by the given [searchQuery] if it is non-null.
|
||||||
@@ -413,6 +462,111 @@ internal interface AppDaoInt : AppDao {
|
|||||||
FROM ${AppMetadata.TABLE} AS app WHERE repoId = :repoId AND packageName = :packageName""")
|
FROM ${AppMetadata.TABLE} AS app WHERE repoId = :repoId AND packageName = :packageName""")
|
||||||
fun getAppOverviewItem(repoId: Long, packageName: String): AppOverviewItem?
|
fun getAppOverviewItem(repoId: Long, packageName: String): AppOverviewItem?
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
override suspend fun getAllApps(): List<AppOverviewItem> {
|
||||||
|
val query = getAppsQuery("") {}
|
||||||
|
return getApps(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
override suspend fun getAppsByAuthor(authorName: String): List<AppOverviewItem> {
|
||||||
|
val query = getAppsQuery("authorName = ?") { statement ->
|
||||||
|
statement.bindText(1, authorName)
|
||||||
|
}
|
||||||
|
return getApps(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
override suspend fun getAppsByCategory(categoryId: String): List<AppOverviewItem> {
|
||||||
|
val query = getAppsQuery("categories LIKE '%,' || ? || ',%'") { statement ->
|
||||||
|
statement.bindText(1, categoryId)
|
||||||
|
}
|
||||||
|
return getApps(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
override suspend fun getNewApps(maxAgeInDays: Long): List<AppOverviewItem> {
|
||||||
|
val query =
|
||||||
|
getAppsQuery("app.added = app.lastUpdated AND app.lastUpdated > ?") { statement ->
|
||||||
|
statement.bindLong(
|
||||||
|
1,
|
||||||
|
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(maxAgeInDays)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return getApps(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
override suspend fun getRecentlyUpdatedApps(limit: Int): List<AppOverviewItem> {
|
||||||
|
val query = getAppsQuery(
|
||||||
|
"app.added != app.lastUpdated ORDER BY app.lastUpdated DESC LIMIT ?"
|
||||||
|
) { statement ->
|
||||||
|
statement.bindInt(1, limit)
|
||||||
|
}
|
||||||
|
return getApps(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("""SELECT repoId, packageName, app.added, app.lastUpdated, localizedName,
|
||||||
|
localizedSummary, categories, version.antiFeatures, app.isCompatible
|
||||||
|
FROM ${AppMetadata.TABLE} AS app
|
||||||
|
LEFT JOIN ${HighestVersion.TABLE} AS version USING (repoId, packageName)
|
||||||
|
WHERE repoId = :repoId""")
|
||||||
|
override suspend fun getAppsByRepository(repoId: Long): List<AppOverviewItem>
|
||||||
|
|
||||||
|
override fun getNewAppsFlow(maxAgeInDays: Long): Flow<List<AppOverviewItem>> {
|
||||||
|
val query =
|
||||||
|
getAppsQuery(
|
||||||
|
"app.added = app.lastUpdated AND app.lastUpdated > ? ORDER BY app.added DESC"
|
||||||
|
) { statement ->
|
||||||
|
statement.bindLong(
|
||||||
|
1,
|
||||||
|
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(maxAgeInDays)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return getAppsFlow(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRecentlyUpdatedAppsFlow(limit: Int): Flow<List<AppOverviewItem>> {
|
||||||
|
val query = getAppsQuery(
|
||||||
|
"app.added != app.lastUpdated ORDER BY app.lastUpdated DESC LIMIT ?"
|
||||||
|
) { statement ->
|
||||||
|
statement.bindInt(1, limit)
|
||||||
|
}
|
||||||
|
return getAppsFlow(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAppsQuery(
|
||||||
|
whereQuery: String,
|
||||||
|
onBindStatement: (SQLiteStatement) -> Unit,
|
||||||
|
): RoomRawQuery {
|
||||||
|
val queryBuilder = StringBuilder(
|
||||||
|
"""
|
||||||
|
SELECT repoId, packageName, app.added, app.lastUpdated, localizedName,
|
||||||
|
localizedSummary, categories, version.antiFeatures, app.isCompatible
|
||||||
|
FROM ${AppMetadata.TABLE} AS app
|
||||||
|
JOIN PreferredRepo USING (packageName)
|
||||||
|
LEFT JOIN ${HighestVersion.TABLE} AS version USING (repoId, packageName)
|
||||||
|
WHERE repoId = preferredRepoId"""
|
||||||
|
)
|
||||||
|
if (whereQuery.isNotEmpty()) queryBuilder.append(" AND ").append(whereQuery)
|
||||||
|
return RoomRawQuery(
|
||||||
|
sql = queryBuilder.toString().trimIndent(),
|
||||||
|
onBindStatement = onBindStatement,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RawQuery
|
||||||
|
suspend fun getApps(query: RoomRawQuery): List<AppOverviewItem>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@RawQuery(
|
||||||
|
observedEntities = [
|
||||||
|
AppMetadata::class, Version::class, Repository::class, RepositoryPreferences::class,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
fun getAppsFlow(query: RoomRawQuery): Flow<List<AppOverviewItem>>
|
||||||
|
|
||||||
//
|
//
|
||||||
// AppListItems
|
// AppListItems
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in new issue
Block a user