diff --git a/index/src/androidMain/kotlin/org/fdroid/index/v1/IndexV1StreamProcessor.kt b/index/src/androidMain/kotlin/org/fdroid/index/v1/IndexV1StreamProcessor.kt index 9295f04ff..af10f6f67 100644 --- a/index/src/androidMain/kotlin/org/fdroid/index/v1/IndexV1StreamProcessor.kt +++ b/index/src/androidMain/kotlin/org/fdroid/index/v1/IndexV1StreamProcessor.kt @@ -11,7 +11,6 @@ import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.decodeFromStream import org.fdroid.index.DEFAULT_LOCALE import org.fdroid.index.IndexParser -import org.fdroid.index.DEFAULT_LOCALE import org.fdroid.index.RELEASE_CHANNEL_BETA import org.fdroid.index.getV1ReleaseChannels import org.fdroid.index.mapInto @@ -22,6 +21,18 @@ import org.fdroid.index.v2.LocalizedTextV2 import org.fdroid.index.v2.PackageVersionV2 import java.io.InputStream +/** + * Processes a indexV1 stream and calls the given [indexStreamReceiver] while parsing it. + * Attention: This requires the following canonical top-level order in the JSON + * as produced by `fdroidserver`. + * * repo + * * requests + * * apps + * * packages + * + * Any other order of those elements will produce unexpected results + * or throw [IllegalArgumentException]. + */ @Suppress("DEPRECATION") @OptIn(ExperimentalSerializationApi::class) public class IndexV1StreamProcessor( @@ -29,37 +40,49 @@ public class IndexV1StreamProcessor( private val certificate: String?, private val locale: String = DEFAULT_LOCALE, private val json: Json = IndexParser.json, - private val getAndLogReadBytes: () -> Long? = { null }, ) { - public fun process(repoId: Long, inputStream: InputStream) { - json.decodeFromStream(IndexStreamSerializer(repoId), inputStream) - getAndLogReadBytes() + public fun process(inputStream: InputStream) { + json.decodeFromStream(IndexStreamSerializer(), inputStream) } - private inner class IndexStreamSerializer(val repoId: Long) : KSerializer { + private inner class IndexStreamSerializer : KSerializer { override val descriptor = IndexV1.serializer().descriptor override fun deserialize(decoder: Decoder): IndexV1? { - getAndLogReadBytes() decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") decoder.beginStructure(descriptor) - var index = decoder.decodeElementIndex(descriptor) - deserializeRepo(decoder, index, repoId) - index = decoder.decodeElementIndex(descriptor) - deserializeRequests(decoder, index, repoId) - index = decoder.decodeElementIndex(descriptor) - val appDataMap = deserializeApps(decoder, index, repoId) - index = decoder.decodeElementIndex(descriptor) - deserializePackages(decoder, index, repoId, appDataMap) + val index0 = decoder.decodeElementIndex(descriptor) + deserializeRepo(decoder, index0) + val index1 = decoder.decodeElementIndex(descriptor) + if (index1 == DECODE_DONE) { + updateRepoData(emptyMap()) + decoder.endStructure(descriptor) + return null + } + deserializeRequests(decoder, index1) + val index2 = decoder.decodeElementIndex(descriptor) + if (index2 == DECODE_DONE) { + updateRepoData(emptyMap()) + decoder.endStructure(descriptor) + return null + } + val appDataMap = deserializeApps(decoder, index2) + val index3 = decoder.decodeElementIndex(descriptor) + if (index3 == DECODE_DONE) { + updateRepoData(appDataMap) + decoder.endStructure(descriptor) + return null + } + deserializePackages(decoder, index3, appDataMap) decoder.endStructure(descriptor) updateRepoData(appDataMap) return null } - private fun deserializeRepo(decoder: JsonDecoder, index: Int, repoId: Long) { + private fun deserializeRepo(decoder: JsonDecoder, index: Int) { require(index == descriptor.getElementIndex("repo")) val repo = decoder.decodeSerializableValue(RepoV1.serializer()) val repoV2 = repo.toRepoV2( @@ -68,32 +91,27 @@ public class IndexV1StreamProcessor( categories = emptyMap(), releaseChannels = emptyMap() ) - indexStreamReceiver.receive(repoId, repoV2, repo.version, certificate) + indexStreamReceiver.receive(repoV2, repo.version, certificate) } - private fun deserializeRequests(decoder: JsonDecoder, index: Int, repoId: Long) { + private fun deserializeRequests(decoder: JsonDecoder, index: Int) { require(index == descriptor.getElementIndex("requests")) decoder.decodeSerializableValue(Requests.serializer()) // we ignore the requests here, don't act on them } - private fun deserializeApps( - decoder: JsonDecoder, - index: Int, - repoId: Long, - ): Map { + private fun deserializeApps(decoder: JsonDecoder, index: Int): Map { require(index == descriptor.getElementIndex("apps")) val appDataMap = HashMap() val mapDescriptor = descriptor.getElementDescriptor(index) val compositeDecoder = decoder.beginStructure(mapDescriptor) while (true) { - getAndLogReadBytes() val packageIndex = compositeDecoder.decodeElementIndex(descriptor) if (packageIndex == DECODE_DONE) break val appV1 = decoder.decodeSerializableElement(descriptor, packageIndex, AppV1.serializer()) val appV2 = appV1.toMetadataV2(null, locale) - indexStreamReceiver.receive(repoId, appV1.packageName, appV2) + indexStreamReceiver.receive(appV1.packageName, appV2) appDataMap[appV1.packageName] = AppData( antiFeatures = appV1.antiFeatures.associateWith { emptyMap() }, whatsNew = appV1.localized?.mapValuesNotNull { it.value.whatsNew }, @@ -108,20 +126,17 @@ public class IndexV1StreamProcessor( private fun deserializePackages( decoder: JsonDecoder, index: Int, - repoId: Long, appDataMap: Map, ) { require(index == descriptor.getElementIndex("packages")) val mapDescriptor = descriptor.getElementDescriptor(index) val compositeDecoder = decoder.beginStructure(mapDescriptor) while (true) { - getAndLogReadBytes() val packageIndex = compositeDecoder.decodeElementIndex(descriptor) if (packageIndex == DECODE_DONE) break readPackageMapEntry( decoder = compositeDecoder as JsonDecoder, index = packageIndex, - repoId = repoId, appDataMap = appDataMap, ) } @@ -131,7 +146,6 @@ public class IndexV1StreamProcessor( private fun readPackageMapEntry( decoder: JsonDecoder, index: Int, - repoId: Long, appDataMap: Map, ) { val packageName = decoder.decodeStringElement(descriptor, index) @@ -142,7 +156,6 @@ public class IndexV1StreamProcessor( val compositeDecoder = decoder.beginStructure(listDescriptor) var isFirstVersion = true while (true) { - getAndLogReadBytes() val packageIndex = compositeDecoder.decodeElementIndex(descriptor) if (packageIndex == DECODE_DONE) break val packageVersionV1 = decoder.decodeSerializableElement( @@ -159,17 +172,13 @@ public class IndexV1StreamProcessor( whatsNew = if (isFirstVersion) appDataMap[packageName]?.whatsNew else null ) if (isFirstVersion) { - indexStreamReceiver.updateAppMetadata( - repoId, - packageName, - packageVersionV1.signer - ) + indexStreamReceiver.updateAppMetadata(packageName, packageVersionV1.signer) } isFirstVersion = false val versionId = packageVersionV2.file.sha256 versions[versionId] = packageVersionV2 } - indexStreamReceiver.receive(repoId, packageName, versions) + indexStreamReceiver.receive(packageName, versions) compositeDecoder.endStructure(listDescriptor) } @@ -177,17 +186,11 @@ public class IndexV1StreamProcessor( val antiFeatures = HashMap() val categories = HashMap() appDataMap.values.forEach { appData -> - appData.antiFeatures.keys.mapInto( - antiFeatures, - AntiFeatureV2(name = emptyMap(), description = emptyMap()) - ) - appData.categories.mapInto( - categories, - CategoryV2(name = emptyMap(), description = emptyMap()) - ) + appData.antiFeatures.mapInto(antiFeatures) + appData.categories.mapInto(categories) } val releaseChannels = getV1ReleaseChannels() - indexStreamReceiver.updateRepo(repoId, antiFeatures, categories, releaseChannels) + indexStreamReceiver.updateRepo(antiFeatures, categories, releaseChannels) } override fun serialize(encoder: Encoder, value: IndexV1?) { diff --git a/index/src/androidMain/kotlin/org/fdroid/index/v2/IndexStreamProcessor.kt b/index/src/androidMain/kotlin/org/fdroid/index/v2/IndexV2StreamProcessor.kt similarity index 68% rename from index/src/androidMain/kotlin/org/fdroid/index/v2/IndexStreamProcessor.kt rename to index/src/androidMain/kotlin/org/fdroid/index/v2/IndexV2StreamProcessor.kt index da0147720..3d122be93 100644 --- a/index/src/androidMain/kotlin/org/fdroid/index/v2/IndexStreamProcessor.kt +++ b/index/src/androidMain/kotlin/org/fdroid/index/v2/IndexV2StreamProcessor.kt @@ -12,26 +12,20 @@ import org.fdroid.index.IndexParser import java.io.InputStream @OptIn(ExperimentalSerializationApi::class) -public class IndexStreamProcessor( - private val indexStreamReceiver: IndexStreamReceiver, +public class IndexV2StreamProcessor( + private val indexStreamReceiver: IndexV2StreamReceiver, private val certificate: String?, private val json: Json = IndexParser.json, - private val getAndLogReadBytes: () -> Long? = { null }, ) { - public fun process(repoId: Long, version: Int, inputStream: InputStream) { - json.decodeFromStream(IndexStreamSerializer(repoId, version), inputStream) - getAndLogReadBytes() + public fun process(version: Int, inputStream: InputStream) { + json.decodeFromStream(IndexStreamSerializer(version), inputStream) } - private inner class IndexStreamSerializer( - val repoId: Long, - val version: Int, - ) : KSerializer { + private inner class IndexStreamSerializer(val version: Int) : KSerializer { override val descriptor = IndexV2.serializer().descriptor override fun deserialize(decoder: Decoder): IndexV2? { - getAndLogReadBytes() decoder as? JsonDecoder ?: error("Can be deserialized only by JSON") decoder.beginStructure(descriptor) @@ -40,49 +34,47 @@ public class IndexStreamProcessor( when (val startIndex = decoder.decodeElementIndex(descriptor)) { repoIndex -> { - deserializeRepo(decoder, startIndex, repoId) + deserializeRepo(decoder, startIndex) val index = decoder.decodeElementIndex(descriptor) - deserializePackages(decoder, index, repoId) + if (index == packagesIndex) deserializePackages(decoder, index) } packagesIndex -> { - deserializePackages(decoder, startIndex, repoId) + deserializePackages(decoder, startIndex) val index = decoder.decodeElementIndex(descriptor) - deserializeRepo(decoder, index, repoId) + if (index == repoIndex) deserializeRepo(decoder, index) } else -> error("Unexpected startIndex: $startIndex") } decoder.endStructure(descriptor) - indexStreamReceiver.onStreamEnded(repoId) + indexStreamReceiver.onStreamEnded() return null } - private fun deserializeRepo(decoder: JsonDecoder, index: Int, repoId: Long) { + private fun deserializeRepo(decoder: JsonDecoder, index: Int) { require(index == descriptor.getElementIndex("repo")) val repo = decoder.decodeSerializableValue(RepoV2.serializer()) - // TODO this replaces the index and thus removes all data, not good when repo is second - indexStreamReceiver.receive(repoId, repo, version, certificate) + indexStreamReceiver.receive(repo, version, certificate) } - private fun deserializePackages(decoder: JsonDecoder, index: Int, repoId: Long) { + private fun deserializePackages(decoder: JsonDecoder, index: Int) { require(index == descriptor.getElementIndex("packages")) val mapDescriptor = descriptor.getElementDescriptor(index) val compositeDecoder = decoder.beginStructure(mapDescriptor) while (true) { - getAndLogReadBytes() val packageIndex = compositeDecoder.decodeElementIndex(descriptor) if (packageIndex == CompositeDecoder.DECODE_DONE) break - readMapEntry(compositeDecoder, packageIndex, repoId) + readMapEntry(compositeDecoder, packageIndex) } compositeDecoder.endStructure(mapDescriptor) } - private fun readMapEntry(decoder: CompositeDecoder, index: Int, repoId: Long) { + private fun readMapEntry(decoder: CompositeDecoder, index: Int) { val packageName = decoder.decodeStringElement(descriptor, index) decoder.decodeElementIndex(descriptor) val packageV2 = decoder.decodeSerializableElement( descriptor, index + 1, PackageV2.serializer() ) - indexStreamReceiver.receive(repoId, packageName, packageV2) + indexStreamReceiver.receive(packageName, packageV2) } override fun serialize(encoder: Encoder, value: IndexV2?) { diff --git a/index/src/androidTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt b/index/src/androidTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt index db15cf5c0..21ef80713 100644 --- a/index/src/androidTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt +++ b/index/src/androidTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt @@ -1,8 +1,5 @@ package org.fdroid.index.v1 -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream import org.fdroid.index.v2.AntiFeatureV2 import org.fdroid.index.v2.CategoryV2 import org.fdroid.index.v2.IndexV2 @@ -11,101 +8,85 @@ import org.fdroid.index.v2.PackageV2 import org.fdroid.index.v2.PackageVersionV2 import org.fdroid.index.v2.ReleaseChannelV2 import org.fdroid.index.v2.RepoV2 -import org.junit.Assume.assumeTrue +import org.fdroid.test.TestDataEmptyV2 +import org.fdroid.test.TestDataMaxV2 +import org.fdroid.test.TestDataMidV2 +import org.fdroid.test.TestDataMinV2 +import org.fdroid.test.v1compat import org.junit.Test import java.io.File import java.io.FileInputStream import kotlin.test.assertEquals import kotlin.test.fail -@OptIn(ExperimentalSerializationApi::class) internal class IndexV1StreamProcessorTest { - private val json = Json - - /** - * Tests that indexV1 parsed with a stream receiver matches the indexV2 parsed normally. - */ @Test - fun testFDroidStreamProcessing() { - val file1 = File("src/commonTest/resources/index-v1.json") - val file2 = File("src/commonTest/resources/index-v2.json") - assumeTrue(file1.isFile) - assumeTrue(file2.isFile) - val indexParsed: IndexV2 = FileInputStream(file2).use { json.decodeFromStream(it) } + fun testEmpty() { + testStreamProcessing("src/sharedTest/resources/index-empty-v1.json", + TestDataEmptyV2.index.v1compat()) + } + @Test + fun testMin() { + testStreamProcessing("src/sharedTest/resources/index-min-v1.json", + TestDataMinV2.index.v1compat()) + } + + @Test + fun testMid() { + testStreamProcessing("src/sharedTest/resources/index-mid-v1.json", + TestDataMidV2.indexCompat) + } + + @Test + fun testMax() { + testStreamProcessing("src/sharedTest/resources/index-max-v1.json", + TestDataMaxV2.indexCompat) + } + + private fun testStreamProcessing(filePath: String, indexV2: IndexV2) { + val file = File(filePath) val testStreamReceiver = TestStreamReceiver() - val streamProcessor = IndexV1StreamProcessor(testStreamReceiver, null, json = json) - FileInputStream(file1).use { streamProcessor.process(1, it) } - - assertEquals(indexParsed.repo, testStreamReceiver.repo) - assertEquals(indexParsed.packages.size, testStreamReceiver.packages.size) - indexParsed.packages.entries.forEach { (packageName, packageV2) -> - assertEquals(packageV2, testStreamReceiver.packages[packageName]) - } - } - - @Test - fun testFDroidArchiveStreamProcessing() { - testStreamProcessing("src/commonTest/resources/fdroid-archive/index-v1.json") - } - - @Test - fun testGuardianStreamProcessing() { - testStreamProcessing("src/commonTest/resources/guardian/index-v1.json") - } - - @Test - fun testIzzyStreamProcessing() { - testStreamProcessing("src/commonTest/resources/izzy/index-v1.json") - } - - @Test - fun testWindStreamProcessing() { - testStreamProcessing("src/commonTest/resources/wind/index-v1.json") - } - - private fun testStreamProcessing(filePath1: String) { - val file1 = File(filePath1) - assumeTrue(file1.isFile) - val testStreamReceiver = TestStreamReceiver() - val streamProcessor = IndexV1StreamProcessor(testStreamReceiver, null, json = json) - FileInputStream(file1).use { streamProcessor.process(1, it) } + val streamProcessor = IndexV1StreamProcessor(testStreamReceiver, null) + FileInputStream(file).use { streamProcessor.process(it) } + assertEquals(indexV2.repo, testStreamReceiver.repo) + assertEquals(indexV2.packages, testStreamReceiver.packages) } + @Suppress("DEPRECATION") private class TestStreamReceiver : IndexV1StreamReceiver { var repo: RepoV2? = null val packages = HashMap() - override fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) { + override fun receive(repo: RepoV2, version: Int, certificate: String?) { this.repo = repo } - override fun receive(repoId: Long, packageId: String, m: MetadataV2) { + override fun receive(packageId: String, m: MetadataV2) { packages[packageId] = PackageV2( metadata = m, versions = emptyMap(), ) } - override fun receive(repoId: Long, packageId: String, v: Map) { + override fun receive(packageId: String, v: Map) { packages[packageId] = packages[packageId]!!.copy(versions = v) } override fun updateRepo( - repoId: Long, antiFeatures: Map, categories: Map, releaseChannels: Map, ) { - repo = repo!!.copy( + repo = repo?.copy( antiFeatures = antiFeatures, categories = categories, releaseChannels = releaseChannels, - ) + ) ?: fail() } - override fun updateAppMetadata(repoId: Long, packageId: String, preferredSigner: String?) { + override fun updateAppMetadata(packageId: String, preferredSigner: String?) { val currentPackage = packages[packageId] ?: fail() packages[packageId] = currentPackage.copy( metadata = currentPackage.metadata.copy(preferredSigner = preferredSigner), diff --git a/index/src/androidTest/kotlin/org/fdroid/index/v1/StreamTest.kt b/index/src/androidTest/kotlin/org/fdroid/index/v1/StreamTest.kt deleted file mode 100644 index ff28abff8..000000000 --- a/index/src/androidTest/kotlin/org/fdroid/index/v1/StreamTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.fdroid.index.v1 - -import io.ktor.utils.io.jvm.javaio.toByteReadChannel -import io.ktor.utils.io.jvm.javaio.toInputStream -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import org.junit.Assume.assumeTrue -import java.io.File -import java.io.FileInputStream -import kotlin.test.Test - -internal class StreamTest { - - @Test - @OptIn(ExperimentalSerializationApi::class) - fun test() = runBlocking { - val file = File("src/commonTest/resources/index-v1.json") - assumeTrue(file.isFile) - val byteChannel = FileInputStream(file).toByteReadChannel() - val index = Json.decodeFromStream(byteChannel.toInputStream()) - - } - -} diff --git a/index/src/androidTest/kotlin/org/fdroid/index/v2/IndexStreamProcessorTest.kt b/index/src/androidTest/kotlin/org/fdroid/index/v2/IndexStreamProcessorTest.kt deleted file mode 100644 index 6824e0481..000000000 --- a/index/src/androidTest/kotlin/org/fdroid/index/v2/IndexStreamProcessorTest.kt +++ /dev/null @@ -1,121 +0,0 @@ -package org.fdroid.index.v2 - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import kotlinx.serialization.json.encodeToStream -import org.junit.Assume.assumeTrue -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue -import kotlin.test.fail - -private const val INDEX_V2 = "src/commonTest/resources/index-v2.json" - -@OptIn(ExperimentalSerializationApi::class) -internal class IndexStreamProcessorTest { - - @get:Rule - var folder: TemporaryFolder = TemporaryFolder() - - private val json = Json - - /** - * Tests that index parsed with a stream receiver matches the index parsed normally. - */ - @Test - fun testStreamProcessing() { - val file = File(INDEX_V2) - assumeTrue(file.isFile) - val indexParsed: IndexV2 = FileInputStream(file).use { json.decodeFromStream(it) } - - val testStreamReceiver = TestStreamReceiver() - val certificate = "foo bar" - val streamProcessor = IndexStreamProcessor(testStreamReceiver, certificate, json) - FileInputStream(file).use { streamProcessor.process(1, 42, it) } - - assertEquals(indexParsed.repo, testStreamReceiver.repo) - assertTrue(testStreamReceiver.calledOnStreamEnded) - assertEquals(certificate, testStreamReceiver.certificate) - assertEquals(indexParsed.packages.size, testStreamReceiver.packages.size) - indexParsed.packages.entries.forEach { (packageName, packageV2) -> - assertEquals(packageV2, testStreamReceiver.packages[packageName]) - } - } - - /** - * Tests that that [IndexStreamProcessor] can handle a re-ordered index, - * i.e. repo only after packages. - */ - @Test - fun testReorderedStreamProcessing() { - val file = File(INDEX_V2) - assumeTrue(file.isFile) - val indexParsed: IndexV2 = FileInputStream(file).use { json.decodeFromStream(it) } - val newFile = folder.newFile() - // write out parsed index in reverse order (repo after packages) - FileOutputStream(newFile).use { outputStream -> - outputStream.write("{ \"packages\": ".encodeToByteArray()) - json.encodeToStream(indexParsed.packages, outputStream) - outputStream.write(", \"repo\": ".encodeToByteArray()) - json.encodeToStream(indexParsed.repo, outputStream) - outputStream.write("}".encodeToByteArray()) - } - val testStreamReceiver = ReorderedTestStreamReceiver() - val certificate = "foo bar" - val streamProcessor = IndexStreamProcessor(testStreamReceiver, certificate, json) - FileInputStream(newFile).use { streamProcessor.process(1, 42, it) } - - assertTrue(testStreamReceiver.repoReceived) - assertTrue(testStreamReceiver.calledOnStreamEnded) - assertEquals(indexParsed.repo, testStreamReceiver.repo) - assertEquals(certificate, testStreamReceiver.certificate) - assertEquals(indexParsed.packages.size, testStreamReceiver.packages.size) - indexParsed.packages.entries.forEach { (packageName, packageV2) -> - assertEquals(packageV2, testStreamReceiver.packages[packageName]) - } - } - - private open class TestStreamReceiver : IndexStreamReceiver { - var repo: RepoV2? = null - var certificate: String? = null - val packages = HashMap() - var calledOnStreamEnded: Boolean = false - - override fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) { - this.repo = repo - this.certificate = certificate - } - - override fun receive(repoId: Long, packageId: String, p: PackageV2) { - packages[packageId] = p - } - - override fun onStreamEnded(repoId: Long) { - if (calledOnStreamEnded) fail() - calledOnStreamEnded = true - } - } - - private class ReorderedTestStreamReceiver : TestStreamReceiver() { - var repoReceived: Boolean = false - - override fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) { - super.receive(repoId, repo, version, certificate) - assertFalse(repoReceived) - repoReceived = true - } - - override fun receive(repoId: Long, packageId: String, p: PackageV2) { - super.receive(repoId, packageId, p) - assertFalse(repoReceived) - } - } - -} diff --git a/index/src/androidTest/kotlin/org/fdroid/index/v2/IndexV2StreamProcessorTest.kt b/index/src/androidTest/kotlin/org/fdroid/index/v2/IndexV2StreamProcessorTest.kt new file mode 100644 index 000000000..4de95c32e --- /dev/null +++ b/index/src/androidTest/kotlin/org/fdroid/index/v2/IndexV2StreamProcessorTest.kt @@ -0,0 +1,87 @@ +package org.fdroid.index.v2 + +import kotlinx.serialization.ExperimentalSerializationApi +import org.fdroid.test.TestDataEmptyV2 +import org.fdroid.test.TestDataMaxV2 +import org.fdroid.test.TestDataMidV2 +import org.fdroid.test.TestDataMinV2 +import org.fdroid.test.TestUtils.getRandomString +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.File +import java.io.FileInputStream +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.fail + +@OptIn(ExperimentalSerializationApi::class) +internal class IndexV2StreamProcessorTest { + + @get:Rule + var folder: TemporaryFolder = TemporaryFolder() + + @Test + fun testEmpty() { + testStreamProcessing("src/sharedTest/resources/index-empty-v2.json", TestDataEmptyV2.index) + } + + @Test + fun testMin() { + testStreamProcessing("src/sharedTest/resources/index-min-v2.json", TestDataMinV2.index) + } + + @Test + fun testMinReordered() { + testStreamProcessing("src/sharedTest/resources/index-min-reordered-v2.json", + TestDataMinV2.index) + } + + @Test + fun testMid() { + testStreamProcessing("src/sharedTest/resources/index-mid-v2.json", TestDataMidV2.index) + } + + @Test + fun testMax() { + testStreamProcessing("src/sharedTest/resources/index-max-v2.json", TestDataMaxV2.index) + } + + /** + * Tests that index parsed with a stream receiver is equal to the expected test data. + */ + fun testStreamProcessing(filePath: String, index: IndexV2) { + val file = File(filePath) + val testStreamReceiver = TestStreamReceiver() + val certificate = getRandomString() + val streamProcessor = IndexV2StreamProcessor(testStreamReceiver, certificate) + FileInputStream(file).use { streamProcessor.process(42, it) } + + assertTrue(testStreamReceiver.calledOnStreamEnded) + assertEquals(index.repo, testStreamReceiver.repo) + assertEquals(certificate, testStreamReceiver.certificate) + assertEquals(index.packages, testStreamReceiver.packages) + } + + private open class TestStreamReceiver : IndexV2StreamReceiver { + var repo: RepoV2? = null + var certificate: String? = null + val packages = HashMap() + var calledOnStreamEnded: Boolean = false + + override fun receive(repo: RepoV2, version: Int, certificate: String?) { + this.repo = repo + this.certificate = certificate + } + + override fun receive(packageId: String, p: PackageV2) { + packages[packageId] = p + } + + override fun onStreamEnded() { + if (calledOnStreamEnded) fail() + calledOnStreamEnded = true + } + } + +} diff --git a/index/src/androidTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt b/index/src/androidTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt index e9d7e420f..35e1ddbef 100644 --- a/index/src/androidTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt +++ b/index/src/androidTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt @@ -1,7 +1,7 @@ package org.fdroid.index.v2 -import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject +import org.fdroid.index.IndexParser.json import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -15,14 +15,14 @@ internal class ReflectionDifferTest { put("en", "bar") put("de-DE", "foo bar") } - val json = """ + val jsonInput = """ { "de": "foo", "en": null, "en-US": "bar" } """.trimIndent() - val diff = Json.parseToJsonElement(json).jsonObject + val diff = json.parseToJsonElement(jsonInput).jsonObject val result = ReflectionDiffer.applyDiff(old, diff) assertEquals(3, result.size) @@ -38,13 +38,13 @@ internal class ReflectionDifferTest { sha256 = "bar", size = Random.nextLong() ) - val json = """ + val jsonInput = """ { "name": "foo bar", "size": null } """.trimIndent() - val diff = Json.parseToJsonElement(json).jsonObject + val diff = json.parseToJsonElement(jsonInput).jsonObject val result = ReflectionDiffer.applyDiff(old, diff) assertEquals("foo bar", result.name) diff --git a/index/src/commonMain/kotlin/org/fdroid/index/IndexConverter.kt b/index/src/commonMain/kotlin/org/fdroid/index/IndexConverter.kt index a77c6489c..1725f1240 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/IndexConverter.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/IndexConverter.kt @@ -39,14 +39,8 @@ public class IndexConverter( it.hash to it.toPackageVersionV2(versionReleaseChannels, appAntiFeatures, wn) } ?: emptyMap(), ) - appAntiFeatures.keys.mapInto( - antiFeatures, - AntiFeatureV2(name = emptyMap(), description = emptyMap()) - ) - app.categories.mapInto( - categories, - CategoryV2(name = emptyMap(), description = emptyMap()) - ) + appAntiFeatures.mapInto(antiFeatures) + app.categories.mapInto(categories) packagesV2[app.packageName] = packageV2 } return IndexV2( @@ -62,14 +56,29 @@ public class IndexConverter( } -internal fun Collection.mapInto(map: HashMap, value: T) { +internal fun Collection.mapInto(map: HashMap, valueGetter: (String) -> T) { forEach { key -> - if (!map.containsKey(key)) map[key] = value + if (!map.containsKey(key)) map[key] = valueGetter(key) } } -internal fun getV1ReleaseChannels() = mapOf( - RELEASE_CHANNEL_BETA to ReleaseChannelV2(emptyMap(), emptyMap()) +internal fun List.mapInto(map: HashMap) { + mapInto(map) { key -> + CategoryV2(name = mapOf(DEFAULT_LOCALE to key)) + } +} + +internal fun Map.mapInto(map: HashMap) { + keys.mapInto(map) { key -> + AntiFeatureV2(name = mapOf(DEFAULT_LOCALE to key)) + } +} + +public fun getV1ReleaseChannels(): Map = mapOf( + RELEASE_CHANNEL_BETA to ReleaseChannelV2( + name = mapOf(DEFAULT_LOCALE to RELEASE_CHANNEL_BETA), + description = emptyMap(), + ) ) internal fun Map.mapValuesNotNull( diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v1/AppV1.kt b/index/src/commonMain/kotlin/org/fdroid/index/v1/AppV1.kt index a8548d290..78750b765 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/v1/AppV1.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/v1/AppV1.kt @@ -78,8 +78,8 @@ public data class AppV1( litecoin = litecoin, flattrID = flattrID, ).takeIf { !it.isNull }, - icon = icon?.let { mapOf(locale to FileV2("/icons/$it")) } - ?: localized.toLocalizedFileV2 { it.icon }, + icon = localized.toLocalizedFileV2 { it.icon } + ?: icon?.let { mapOf(locale to FileV2("/icons/$it")) }, featureGraphic = localized.toLocalizedFileV2 { it.featureGraphic }, promoGraphic = localized.toLocalizedFileV2 { it.promoGraphic }, tvBanner = localized.toLocalizedFileV2 { it.tvBanner }, diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1.kt b/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1.kt index 6c21993d4..fdb627f2b 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1.kt @@ -13,9 +13,9 @@ import org.fdroid.index.v2.RepoV2 @Serializable public data class IndexV1( val repo: RepoV1, - val requests: Requests, - val apps: List, - val packages: Map>, + val requests: Requests = Requests(emptyList(), emptyList()), + val apps: List = emptyList(), + val packages: Map> = emptyMap(), ) @Serializable diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1StreamReceiver.kt b/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1StreamReceiver.kt index 1de0a401f..803cb210c 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1StreamReceiver.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/v1/IndexV1StreamReceiver.kt @@ -10,12 +10,11 @@ import org.fdroid.index.v2.RepoV2 @Deprecated("Use IndexV2 instead") public interface IndexV1StreamReceiver { - public fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) - public fun receive(repoId: Long, packageId: String, m: MetadataV2) - public fun receive(repoId: Long, packageId: String, v: Map) + public fun receive(repo: RepoV2, version: Int, certificate: String?) + public fun receive(packageId: String, m: MetadataV2) + public fun receive(packageId: String, v: Map) public fun updateRepo( - repoId: Long, antiFeatures: Map, categories: Map, releaseChannels: Map, @@ -25,6 +24,6 @@ public interface IndexV1StreamReceiver { * Updates [MetadataV2.preferredSigner] with the given [preferredSigner] * for the given [packageId]. */ - public fun updateAppMetadata(repoId: Long, packageId: String, preferredSigner: String?) + public fun updateAppMetadata(packageId: String, preferredSigner: String?) } diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v1/PackageV1.kt b/index/src/commonMain/kotlin/org/fdroid/index/v1/PackageV1.kt index 68a61cbe6..f155d969d 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/v1/PackageV1.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/v1/PackageV1.kt @@ -1,12 +1,16 @@ package org.fdroid.index.v1 +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.descriptors.element import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.encodeCollection +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.intOrNull import kotlinx.serialization.json.jsonArray @@ -84,7 +88,7 @@ public data class PackageV1( @Serializable(with = PermissionV1Serializer::class) public data class PermissionV1( val name: String, - val maxSdk: Int?, + val maxSdk: Int? = null, ) internal class PermissionV1Serializer : KSerializer { @@ -102,8 +106,12 @@ internal class PermissionV1Serializer : KSerializer { return PermissionV1(name, maxSdk) } + @OptIn(ExperimentalSerializationApi::class) override fun serialize(encoder: Encoder, value: PermissionV1) { - TODO("Not yet implemented") + encoder.encodeCollection(JsonArray.serializer().descriptor, 2) { + encodeStringElement(descriptor, 0, value.name) + encodeNullableSerializableElement(descriptor, 1, Int.serializer(), value.maxSdk) + } } } diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexStreamReceiver.kt b/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexStreamReceiver.kt deleted file mode 100644 index 0952815c9..000000000 --- a/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexStreamReceiver.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.fdroid.index.v2 - -public interface IndexStreamReceiver { - - public fun receive(repoId: Long, repo: RepoV2, version: Int, certificate: String?) - public fun receive(repoId: Long, packageId: String, p: PackageV2) - public fun onStreamEnded(repoId: Long) - -} diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2.kt b/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2.kt index 2445c3737..11df15b07 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2.kt @@ -21,7 +21,7 @@ public data class FileV2( @Serializable public data class IndexV2( val repo: RepoV2, - val packages: Map, + val packages: Map = emptyMap(), ) @Serializable diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2StreamReceiver.kt b/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2StreamReceiver.kt new file mode 100644 index 000000000..5cd10243d --- /dev/null +++ b/index/src/commonMain/kotlin/org/fdroid/index/v2/IndexV2StreamReceiver.kt @@ -0,0 +1,22 @@ +package org.fdroid.index.v2 + +public interface IndexV2StreamReceiver { + + /** + * Receives the [RepoV2] from the index stream. + * Attention: This might get called after receiving packages. + */ + public fun receive(repo: RepoV2, version: Int, certificate: String?) + + /** + * Receives one [PackageV2] from the index stream. + * This is called once for each package in the index. + */ + public fun receive(packageId: String, p: PackageV2) + + /** + * Called when the stream has been processed to its end. + */ + public fun onStreamEnded() + +} diff --git a/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt b/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt index c93f5bdae..aea83b28c 100644 --- a/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt +++ b/index/src/commonMain/kotlin/org/fdroid/index/v2/PackageV2.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable public data class PackageV2( val metadata: MetadataV2, - val versions: Map, + val versions: Map = emptyMap(), ) @Serializable diff --git a/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt b/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt index 83cafd5ed..b3cf9d28e 100644 --- a/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt +++ b/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt @@ -1,40 +1,51 @@ package org.fdroid.index import com.goncalossilva.resources.Resource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import org.fdroid.index.v1.IndexV1 +import org.fdroid.index.IndexParser.parseV1 import org.fdroid.index.v2.IndexV2 -import org.junit.Assume.assumeTrue +import org.fdroid.test.TestDataEmptyV2 +import org.fdroid.test.TestDataMaxV2 +import org.fdroid.test.TestDataMidV2 +import org.fdroid.test.TestDataMinV2 +import org.fdroid.test.TestUtils.sorted +import org.fdroid.test.v1compat import kotlin.test.Test import kotlin.test.assertEquals internal class IndexConverterTest { @Test - fun testToIndexV2() { - val res1 = Resource("src/commonTest/resources/index-v1.json") - assumeTrue(res1.exists()) - val indexV1Str = res1.readText() - val indexV1 = Json.decodeFromString(indexV1Str) + fun testEmpty() { + testConversation("src/sharedTest/resources/index-empty-v1.json", + TestDataEmptyV2.index.v1compat()) + } + + @Test + fun testMin() { + testConversation("src/sharedTest/resources/index-min-v1.json", + TestDataMinV2.index.v1compat()) + } + + @Test + fun testMid() { + testConversation("src/sharedTest/resources/index-mid-v1.json", + TestDataMidV2.indexCompat) + } + + @Test + fun testMax() { + testConversation("src/sharedTest/resources/index-max-v1.json", + TestDataMaxV2.indexCompat) + } + + private fun testConversation(file: String, expectedIndex: IndexV2) { + val indexV1Res = Resource(file) + val indexV1Str = indexV1Res.readText() + val indexV1 = parseV1(indexV1Str) val v2 = IndexConverter().toIndexV2(indexV1) - val res2 = Resource("src/commonTest/resources/index-v2.json") - assumeTrue(res2.exists()) - val indexV2Str = res2.readText() - val indexV2 = Json.decodeFromString(indexV2Str) - - assertEquals( - indexV2.repo, - v2.repo.copy( - antiFeatures = emptyMap(), categories = emptyMap(), releaseChannels = emptyMap() - ) - ) // TODO remove copies when test data is fixed - assertEquals(indexV2.packages.size, v2.packages.size) - indexV2.packages.keys.forEach { packageName -> - assertEquals(indexV2.packages[packageName], v2.packages[packageName]) - } + assertEquals(expectedIndex.sorted(), v2.sorted()) } } diff --git a/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt b/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt index a42ce7f33..82ce9f95f 100644 --- a/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt +++ b/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt @@ -1,40 +1,46 @@ package org.fdroid.index.v1 import com.goncalossilva.resources.Resource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import org.junit.Assume.assumeTrue +import org.fdroid.index.IndexParser.parseV1 +import org.fdroid.test.TestDataEmptyV1 +import org.fdroid.test.TestDataMaxV1 +import org.fdroid.test.TestDataMidV1 +import org.fdroid.test.TestDataMinV1 import kotlin.test.Test +import kotlin.test.assertEquals internal class IndexV1Test { @Test - fun testIndexV1() { - val indexRes = Resource("src/commonTest/resources/index-v1.json") - assumeTrue(indexRes.exists()) + fun testIndexEmptyV1() { + val indexRes = Resource("src/sharedTest/resources/index-empty-v1.json") val indexStr = indexRes.readText() - val index = Json.decodeFromString(indexStr) + val index = parseV1(indexStr) + assertEquals(TestDataEmptyV1.index, index) + } - val indexArchiveRes = Resource("src/commonTest/resources/fdroid-archive/index-v1.json") - assumeTrue(indexArchiveRes.exists()) - val indexArchiveStr = indexArchiveRes.readText() - assumeTrue(indexRes.exists()) - Json.decodeFromString(indexArchiveStr) + @Test + fun testIndexMinV1() { + val indexRes = Resource("src/sharedTest/resources/index-min-v1.json") + val indexStr = indexRes.readText() + val index = parseV1(indexStr) + assertEquals(TestDataMinV1.index, index) + } - val indexGuardianRes = Resource("src/commonTest/resources/guardian/index-v1.json") - assumeTrue(indexGuardianRes.exists()) - val indexGuardianStr = indexGuardianRes.readText() - Json.decodeFromString(indexGuardianStr) + @Test + fun testIndexMidV1() { + val indexRes = Resource("src/sharedTest/resources/index-mid-v1.json") + val indexStr = indexRes.readText() + val index = parseV1(indexStr) + assertEquals(TestDataMidV1.index, index) + } - val indexIzzyRes = Resource("src/commonTest/resources/izzy/index-v1.json") - assumeTrue(indexIzzyRes.exists()) - val indexIzzyStr = indexIzzyRes.readText() - Json.decodeFromString(indexIzzyStr) - - val indexWindRes = Resource("src/commonTest/resources/wind/index-v1.json") - assumeTrue(indexWindRes.exists()) - val indexWindStr = indexWindRes.readText() - Json.decodeFromString(indexWindStr) + @Test + fun testIndexMaxV1() { + val indexRes = Resource("src/sharedTest/resources/index-max-v1.json") + val indexStr = indexRes.readText() + val index = parseV1(indexStr) + assertEquals(TestDataMaxV1.index, index) } } diff --git a/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt b/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt index 140170d7c..3ab7c30e5 100644 --- a/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt +++ b/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt @@ -1,33 +1,48 @@ package org.fdroid.index.v2 import com.goncalossilva.resources.Resource -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import org.junit.Assume.assumeTrue +import org.fdroid.index.IndexParser.parseV2 +import org.fdroid.test.TestDataEmptyV2 +import org.fdroid.test.TestDataMaxV2 +import org.fdroid.test.TestDataMidV2 +import org.fdroid.test.TestDataMinV2 import kotlin.test.Test +import kotlin.test.assertEquals internal class IndexV2Test { @Test - fun testIndexV2() { - val entryRes = Resource("src/commonTest/resources/entry.json") - assumeTrue(entryRes.exists()) - val entryStr = entryRes.readText() - val entry = Json.decodeFromString(entryStr) - - val indexRes = Resource("src/commonTest/resources/index-v2.json") - assumeTrue(indexRes.exists()) - val indexStr = indexRes.readText() - val index = Json.decodeFromString(indexStr) + fun testEmpty() { + testIndexEquality("src/sharedTest/resources/index-empty-v2.json", TestDataEmptyV2.index) } @Test - fun testDiffV2() { -// val diff1Res = Resource("src/commonTest/resources/tmp.json") -// assumeTrue(diff1Res.exists()) -// val diff1Str = diff1Res.readText() -// val diff1 = Json.decodeFromString(diff1Str) -// println(diff1) + fun testMin() { + testIndexEquality("src/sharedTest/resources/index-min-v2.json", TestDataMinV2.index) + } + + @Test + fun testMinReordered() { + testIndexEquality("src/sharedTest/resources/index-min-reordered-v2.json", + TestDataMinV2.index) + } + + @Test + fun testMid() { + testIndexEquality("src/sharedTest/resources/index-mid-v2.json", TestDataMidV2.index) + } + + @Test + fun testMax() { + testIndexEquality("src/sharedTest/resources/index-max-v2.json", TestDataMaxV2.index) + } + + private fun testIndexEquality(file: String, expectedIndex: IndexV2) { + val indexV2Res = Resource(file) + val indexV2Str = indexV2Res.readText() + val indexV2 = parseV2(indexV2Str) + + assertEquals(expectedIndex, indexV2) } } diff --git a/index/src/sharedTest/kotlin/org/fdroid/test/TestDataV1.kt b/index/src/sharedTest/kotlin/org/fdroid/test/TestDataV1.kt new file mode 100644 index 000000000..2c2109cc2 --- /dev/null +++ b/index/src/sharedTest/kotlin/org/fdroid/test/TestDataV1.kt @@ -0,0 +1,615 @@ +package org.fdroid.test + +import org.fdroid.index.v1.AppV1 +import org.fdroid.index.v1.IndexV1 +import org.fdroid.index.v1.Localized +import org.fdroid.index.v1.PackageV1 +import org.fdroid.index.v1.PermissionV1 +import org.fdroid.index.v1.RepoV1 +import org.fdroid.index.v1.Requests + +internal object TestDataEmptyV1 { + val repo = RepoV1( + timestamp = 23, + version = 23, + name = "EmptyV1", + icon = "empty-v1.png", + address = "https://empty-v1.org", + description = "This is a repo with empty data.", + ) + val index = IndexV1( + repo = repo, + ) +} + +internal object TestDataMinV1 { + + val repo = RepoV1( + timestamp = 42, + version = 1, + name = "MinV1", + icon = "min-v1.png", + address = "https://min-v1.org", + description = "This is a repo with minimal data.", + ) + + const val packageName = "org.fdroid.min1" + val app = AppV1( + packageName = packageName, + categories = emptyList(), + antiFeatures = emptyList(), + license = "", + ) + val apps = listOf(app) + + val version = PackageV1( + packageName = packageName, + apkName = "${packageName}_23.apk", + hash = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + hashType = "sha256", + size = 1337, + versionName = "0", + ) + val versions = listOf(version) + val packages = mapOf(packageName to versions) + + val index = IndexV1( + repo = repo, + requests = Requests(emptyList(), emptyList()), + apps = apps, + packages = packages, + ) +} + +internal object TestDataMidV1 { + + val repo = RepoV1( + timestamp = 1337, + version = 1, + maxAge = 23, + name = "MidV1", + icon = "mid-v1.png", + address = "https://mid-v1.org", + description = "This is a repo with medium data.", + mirrors = listOf("https://mid-v1.com"), + ) + + const val packageName1 = TestDataMinV1.packageName + const val packageName2 = "org.fdroid.fdroid" + val categories = listOf("Cat1", "Cat2", "Cat3") + val app1 = TestDataMinV1.app.copy( + packageName = packageName1, + categories = listOf(categories[0]), + antiFeatures = listOf("AntiFeature"), + summary = "App1 summary", + description = "App1 description", + name = "App1", + authorName = "App1 author", + license = "GPLv3", + webSite = "http://min1.test.org", + added = 1234567890, + icon = "icon-min1.png", + lastUpdated = 1234567891, + localized = mapOf( + "de" to Localized( + description = "App1 beschreibung", + name = "app 1 name", + summary = "App1 Zusammenfassung", + ), + ), + ) + val app2 = AppV1( + categories = listOf("System"), + antiFeatures = emptyList(), + changelog = "https://gitlab.com/fdroid/fdroidclient/raw/HEAD/CHANGELOG.md", + translation = "https://hosted.weblate.org/projects/f-droid/f-droid", + issueTracker = "https://gitlab.com/fdroid/fdroidclient/issues", + sourceCode = "https://gitlab.com/fdroid/fdroidclient", + donate = "https://f-droid.org/donate", + liberapayID = "27859", + openCollective = "F-Droid-Euro", + flattrID = "343053", + suggestedVersionName = "1.14", + suggestedVersionCode = "1014050", + license = "GPL-3.0-or-later", + webSite = "https://f-droid.org", + added = 1295222400000, + icon = "org.fdroid.fdroid.1014050.png", + packageName = "org.fdroid.fdroid", + lastUpdated = 1643250075000, + localized = mapOf( + "af" to Localized( + description = "F-Droid is 'n installeerbare katalogus van gratis sagteware", + name = "-درويد", + summary = "متجر التطبيقات الذي يحترم الحرية والخصوصية)", + ), + "be" to Localized( + name = "F-Droid", + summary = "Крама праграм, якая паважае свабоду і прыватнасць", + ), + "bg" to Localized( + summary = "Магазинът за приложения, който уважава независимостта и поверителността", + ), + "bn" to Localized( + name = "এফ-ড্রয়েড", + summary = "যে অ্যাপ স্টোর স্বাধীনতা ও গোপনীয়তা সম্মান করে" + ), + "bo" to Localized( + description = "ཨེཕ་རོཌ་ནི་ཨེན་ཀྲོཌ་བབ་སྟེགས་ཀྱི་ཆེད་དུ་FOSS", + summary = "རང་དབང་དང་གསང་དོན་ལ་གུས་བརྩི་ཞུས་མཁན་གྱི་མཉེན་ཆས་ཉར་ཚགས་ཁང་།", + ), + "ca" to Localized( + description = "F-Droid és un catàleg instal·lable d'aplicacions de software lliure", + name = "F-Droid", + summary = "La botiga d'aplicacions que respecta la llibertat i la privacitat", + ), + "cs" to Localized( + description = "F-Droid je instalovatelný katalog softwarových libre", + name = "F-Droid", + summary = "Zdroj aplikací který respektuje vaši svobodu a soukromí", + ), + "cy" to Localized( + description = "Mae F-Droid yn gatalog y gellir ei osod o apiau meddalwedd rhydd" + + "ar gyfer Android.", + name = "F-Droid", + summary = "Yr ystorfa apiau sy'n parchu rhyddid a phreifatrwydd", + ), + "de" to Localized( + description = "F-Droid ist ein installierbarer Katalog mit Libre Software" + + "Android-Apps.", + summary = "Der App-Store, der Freiheit und Privatsphäre respektiert", + ), + "el" to Localized( + description = "Το F-Droid είναι ένας κατάλογος εφαρμογών ελεύθερου λογισμικού", + name = "F-Droid", + summary = "Το κατάστημα εφαρμογών που σέβεται την ελευθερία και την ιδιωτικότητα", + ), + "en-US" to Localized( + description = "F-Droid is an installable catalogue of libre software", + name = "F-Droid", + whatsNew = "* Overhaul Share menu to use built-in options like Nearby", + phoneScreenshots = listOf( + "screenshot-app-details.png", + "screenshot-dark-details.png", + "screenshot-dark-home.png", + "screenshot-dark-knownvuln.png", + "screenshot-knownvuln.png", + "screenshot-search.png", + "screenshot-updates.png", + ), + featureGraphic = "featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + summary = "The app store that respects freedom and privacy", + ), + "eo" to Localized( + description = "F-Droid estas instalebla katalogo de liberaj aplikaĵoj por Android.", + name = "F-Droid", + whatsNew = "• rekonstruita menuo “Kunhavigi” por uzi enkonstruitajn eblojn, ekz.", + summary = "Aplikaĵa vendejo respektanta liberecon kaj privatecon", + ), + ), + ) + val apps = listOf(app1, app2) + + val version1_1 = TestDataMinV1.version.copy( + added = 2342, + apkName = "${packageName1}_23_2.apk", + size = 1338, + srcName = "${packageName1}_23_2.zip", + usesPermission = listOf(PermissionV1("perm")), + usesPermission23 = emptyList(), + versionCode = 1, + versionName = "1", + nativeCode = listOf("x86"), + features = listOf("feature"), + antiFeatures = listOf("anti-feature"), + ) + val version1_2 = PackageV1( + packageName = packageName1, + apkName = "${packageName1}_42.apk", + hash = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + hashType = "sha256", + minSdkVersion = 21, + maxSdkVersion = 4568, + targetSdkVersion = 32, + sig = "old", + signer = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1337, + srcName = "${packageName1}_42.zip", + versionCode = 24, + versionName = "24", + ) + + val version2_1 = PackageV1( + added = 1643250075000, + apkName = "org.fdroid.fdroid_1014050.apk", + hash = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + hashType = "sha256", + minSdkVersion = 22, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8165518, + srcName = "org.fdroid.fdroid_1014050_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.INTERNET"), + PermissionV1(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV1(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV1(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV1(name = "android.permission.BLUETOOTH"), + PermissionV1(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV1(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_SETTINGS"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermission23 = listOf(PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION")), + versionCode = 1014050, + versionName = "1.14", + ) + val version2_2 = PackageV1( + added = 1642785071000, + apkName = "org.fdroid.fdroid_1014005.apk", + hash = "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d", + hashType = "sha256", + minSdkVersion = 22, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8382606, + srcName = "org.fdroid.fdroid_1014005_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.INTERNET"), + PermissionV1(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV1(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV1(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV1(name = "android.permission.BLUETOOTH"), + PermissionV1(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV1(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_SETTINGS"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermission23 = listOf(PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION")), + versionCode = 1014005, + versionName = "1.14-alpha5", + nativeCode = listOf("fakeNativeCode"), + features = listOf("fake feature"), + antiFeatures = listOf("FakeAntiFeature") + ) + val version2_3 = PackageV1( + added = 1635169849000, + apkName = "org.fdroid.fdroid_1014003.apk", + hash = "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3", + hashType = "sha256", + minSdkVersion = 22, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8276110, + srcName = "org.fdroid.fdroid_1014003_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.INTERNET"), + PermissionV1(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV1(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV1(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV1(name = "android.permission.BLUETOOTH"), + PermissionV1(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV1(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_SETTINGS"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE"), + ), + usesPermission23 = listOf(PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION")), + versionCode = 1014003, + versionName = "1.14-alpha3", + ) + val version2_4 = PackageV1( + added = 1632281731000, + apkName = "org.fdroid.fdroid_1014002.apk", + hash = "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1", + hashType = "sha256", + minSdkVersion = 22, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8284386, + srcName = "org.fdroid.fdroid_1014002_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.INTERNET"), + PermissionV1(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV1(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV1(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV1(name = "android.permission.BLUETOOTH"), + PermissionV1(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV1(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_SETTINGS"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE"), + ), + usesPermission23 = listOf(PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION")), + versionCode = 1014002, + versionName = "1.14-alpha2", + ) + val version2_5 = PackageV1( + added = 1632281729000, + apkName = "org.fdroid.fdroid_1014001.apk", + hash = "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610", + hashType = "sha256", + minSdkVersion = 22, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8272166, + srcName = "org.fdroid.fdroid_1014001_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.INTERNET"), + PermissionV1(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV1(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV1(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV1(name = "android.permission.BLUETOOTH"), + PermissionV1(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV1(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_SETTINGS"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE"), + ), + usesPermission23 = listOf(PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION")), + versionCode = 1014001, + versionName = "1.14-alpha1", + ) + val versions1 = listOf(version1_1, version1_2) + val versions2 = listOf(version2_1, version2_2, version2_3, version2_4, version2_5) + val packages = mapOf( + packageName1 to versions1, + packageName2 to versions2, + ) + + val index = IndexV1( + repo = repo, + requests = Requests(listOf("installThis"), listOf("uninstallThis")), + apps = apps, + packages = packages, + ) + +} + +internal object TestDataMaxV1 { + + val repo = RepoV1( + timestamp = Long.MAX_VALUE, + version = Int.MAX_VALUE, + maxAge = Int.MAX_VALUE, + name = "MaxV1", + icon = "max-v1.png", + address = "https://max-v1.org", + description = "This is a repo with maximum data.", + mirrors = listOf("https://max-v1.com", "https://max-v1.org"), + ) + + const val packageName1 = TestDataMidV1.packageName1 + const val packageName2 = TestDataMidV1.packageName2 + const val packageName3 = "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moo" + + "dahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5Ung" + + "ohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raeph" + + "oowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y" + val categories = listOf("Cat1", "Cat2", "Cat3") + + val app2 = TestDataMidV1.app2.copy( + categories = listOf("NoMoreSystem", "OneMore"), + antiFeatures = listOf("AddOne"), + summary = "new summary", + description = "new description", + webSite = "https://fdroid.org", + binaries = "https://fdroid.org/binaries", + name = "F-DroidX", + localized = mapOf( + "ch" to Localized( + description = "new desc", + name = "new name", + summary = "new summary", + tenInchScreenshots = listOf("new screenshots"), + whatsNew = "This is new!" + ), + "de" to Localized( + summary = "Der App-Store, der Freiheit und Privatsphäre respektiert", + ), + "en-US" to Localized( + description = "F-Droid is an installable catalogue of libre software", + summary = "new summary in en-US", + phoneScreenshots = listOf( + "screenshot-app-details.png", + "screenshot-dark-details.png", + "screenshot-dark-home.png", + "screenshot-search.png", + "screenshot-updates.png", + ), + featureGraphic = "featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + icon = "new icon", + ), + ), + ) + val app3 = AppV1( + packageName = packageName3, + categories = categories, + antiFeatures = listOf("AntiFeature", "NonFreeNet", "NotNice", "VeryBad", "Dont,Show,This"), + summary = "App3 summary", + description = "App3 description", + changelog = "changeLog3", + translation = "translation3", + issueTracker = "tracker3", + sourceCode = "source code3", + binaries = "binaries3", + name = "App3", + authorName = "App3 author", + authorEmail = "email", + authorWebSite = "website", + authorPhone = "phone", + donate = "donate", + liberapayID = "liberapayID", + liberapay = "liberapay", + openCollective = "openCollective", + bitcoin = "bitcoin", + litecoin = "litecoin", + flattrID = "flattrID", + suggestedVersionName = "1.0", + suggestedVersionCode = Long.MIN_VALUE.toString(), + license = "GPLv3", + webSite = "http://min1.test.org", + added = 1234567890, + icon = "icon-max1.png", + lastUpdated = Long.MAX_VALUE, + localized = mapOf( + LOCALE to Localized( + whatsNew = "this is new", + ), + "de" to Localized( + whatsNew = "das ist neu", + ), + "en" to Localized( + description = "en ", + name = "en ", + icon = "en ", + video = "en ", + phoneScreenshots = listOf("en phoneScreenshots", "en phoneScreenshots2"), + sevenInchScreenshots = listOf("en sevenInchScreenshots", + "en sevenInchScreenshots2"), + tenInchScreenshots = listOf("en tenInchScreenshots", "en tenInchScreenshots2"), + wearScreenshots = listOf("en wearScreenshots", "en wearScreenshots2"), + tvScreenshots = listOf("en tvScreenshots", "en tvScreenshots2"), + featureGraphic = "en ", + promoGraphic = "en ", + tvBanner = "en ", + summary = "en ", + ) + ), + allowedAPKSigningKeys = listOf("key1, key2"), + ) + val apps = listOf(TestDataMidV1.app1, app2, app3) + + val version2_2 = TestDataMidV1.version2_2.copy( + usesPermission = emptyList(), + usesPermission23 = emptyList(), + nativeCode = emptyList(), + features = emptyList(), + antiFeatures = emptyList(), + ) + val version2_3 = TestDataMidV1.version2_3.copy( + minSdkVersion = 22, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8276110, + srcName = "org.fdroid.fdroid_1014003_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.ACCESS_MEDIA"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE", maxSdk = 32), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.READ_MY_ASS"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE"), + ), + usesPermission23 = listOf( + PermissionV1(name = "android.permission.ACCESS_FINE_LOCATION"), + PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION", maxSdk = 3), + ), + versionCode = 1014003, + versionName = "1.14-alpha3", + ) + val version3_1 = PackageV1( + added = 1643250075000, + apkName = "org.fdroid.fdroid_1014050.apk", + hash = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + hashType = "sha256", + minSdkVersion = 22, + maxSdkVersion = Int.MAX_VALUE, + targetSdkVersion = 25, + packageName = "org.fdroid.fdroid", + sig = "9063aaadfff9cfd811a9c72fb5012f28", + signer = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + size = 8165518, + srcName = "org.fdroid.fdroid_1014050_src.tar.gz", + usesPermission = listOf( + PermissionV1(name = "android.permission.INTERNET"), + PermissionV1(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV1(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV1(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV1(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV1(name = "android.permission.BLUETOOTH"), + PermissionV1(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV1(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV1(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV1(name = "android.permission.WRITE_SETTINGS"), + PermissionV1(name = "android.permission.NFC"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = 22), + PermissionV1(name = "android.permission.WAKE_LOCK"), + PermissionV1(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermission23 = listOf( + PermissionV1(name = "android.permission.ACCESS_COARSE_LOCATION"), + PermissionV1(name = "android.permission.USB_PERMISSION", maxSdk = Int.MAX_VALUE), + ), + versionCode = 1014050, + versionName = "1.14", + nativeCode = listOf("x86", "x86_64"), + features = listOf("feature", "feature2"), + antiFeatures = listOf("anti-feature", "anti-feature2"), + ) + val versions1 = TestDataMidV1.versions1 + val versions2 = listOf( + version2_2, version2_3, TestDataMidV1.version2_4, TestDataMidV1.version2_5 + ) + val versions3 = listOf(version3_1) + val packages = mapOf( + packageName1 to versions1, + packageName2 to versions2, + packageName3 to versions3, + ) + + val index = IndexV1( + repo = repo, + requests = Requests(listOf("installThis", "installThat"), listOf("uninstallThis")), + apps = apps, + packages = packages, + ) +} diff --git a/index/src/sharedTest/kotlin/org/fdroid/test/TestDataV2.kt b/index/src/sharedTest/kotlin/org/fdroid/test/TestDataV2.kt new file mode 100644 index 000000000..4b3bbbf1b --- /dev/null +++ b/index/src/sharedTest/kotlin/org/fdroid/test/TestDataV2.kt @@ -0,0 +1,1260 @@ +package org.fdroid.test + +import org.fdroid.index.RELEASE_CHANNEL_BETA +import org.fdroid.index.getV1ReleaseChannels +import org.fdroid.index.v2.AntiFeatureV2 +import org.fdroid.index.v2.Author +import org.fdroid.index.v2.CategoryV2 +import org.fdroid.index.v2.Donation +import org.fdroid.index.v2.FeatureV2 +import org.fdroid.index.v2.FileV1 +import org.fdroid.index.v2.FileV2 +import org.fdroid.index.v2.IndexV2 +import org.fdroid.index.v2.ManifestV2 +import org.fdroid.index.v2.MetadataV2 +import org.fdroid.index.v2.MirrorV2 +import org.fdroid.index.v2.PackageV2 +import org.fdroid.index.v2.PackageVersionV2 +import org.fdroid.index.v2.PermissionV2 +import org.fdroid.index.v2.ReleaseChannelV2 +import org.fdroid.index.v2.RepoV2 +import org.fdroid.index.v2.Screenshots +import org.fdroid.index.v2.SignatureV2 +import org.fdroid.index.v2.UsesSdkV2 + +internal const val LOCALE = "en-US" + +internal fun IndexV2.v1compat() = copy( + repo = repo.v1compat(), +) + +internal fun RepoV2.v1compat() = copy( + description = description.filterKeys { it == LOCALE }, + icon = icon?.v1compat(), + webBaseUrl = null, + mirrors = mirrors.map { MirrorV2(it.url) }, + categories = categories.mapValues { CategoryV2(name = mapOf(LOCALE to it.key)) }, + releaseChannels = getV1ReleaseChannels(), + antiFeatures = antiFeatures.mapValues { AntiFeatureV2(name = mapOf(LOCALE to it.key)) }, +) + +internal fun PackageV2.v1compat(overrideLocale: Boolean = false) = copy( + metadata = metadata.copy( + name = if (overrideLocale) metadata.name?.filterKeys { it == LOCALE } else metadata.name, + summary = if (overrideLocale) metadata.summary?.filterKeys { it == LOCALE } + else metadata.summary, + description = if (overrideLocale) metadata.description?.filterKeys { it == LOCALE } + else metadata.description, + icon = metadata.icon?.mapValues { it.value.v1compat() }, + featureGraphic = metadata.featureGraphic?.mapValues { it.value.v1compat() }, + promoGraphic = metadata.promoGraphic?.mapValues { it.value.v1compat() }, + tvBanner = metadata.tvBanner?.mapValues { it.value.v1compat() }, + screenshots = metadata.screenshots?.copy( + phone = metadata.screenshots?.phone?.mapValues { list -> + list.value.map { it.v1compat() } + }, + sevenInch = metadata.screenshots?.sevenInch?.mapValues { list -> + list.value.map { it.v1compat() } + }, + tenInch = metadata.screenshots?.tenInch?.mapValues { list -> + list.value.map { it.v1compat() } + }, + wear = metadata.screenshots?.wear?.mapValues { list -> + list.value.map { it.v1compat() } + }, + tv = metadata.screenshots?.tv?.mapValues { list -> + list.value.map { it.v1compat() } + }, + ) + ) +) + +internal fun PackageVersionV2.v1compat() = copy( + src = src?.v1compat(), + manifest = manifest.copy( + signer = if (manifest.signer?.sha256?.size ?: 0 <= 1) manifest.signer else { + SignatureV2(manifest.signer?.sha256?.subList(0, 1) ?: error("")) + } + ), + releaseChannels = releaseChannels.filter { it == RELEASE_CHANNEL_BETA }, + antiFeatures = antiFeatures.mapValues { emptyMap() } +) + +internal fun FileV2.v1compat() = copy( + sha256 = null, + size = null, +) + +internal object TestDataEmptyV2 { + + val repo = RepoV2( + timestamp = 23, + address = "https://empty-v1.org", + name = "EmptyV1", + icon = FileV2( + name = "/icons/empty-v1.png", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 32492, + ), + webBaseUrl = null, + description = mapOf(LOCALE to "This is a repo with empty data."), + mirrors = emptyList(), + antiFeatures = emptyMap(), + categories = emptyMap(), + releaseChannels = emptyMap(), + ) + val index = IndexV2( + repo = repo, + packages = emptyMap(), + ) +} + +internal object TestDataMinV2 { + + val repo = RepoV2( + timestamp = 42, + name = "MinV1", + icon = FileV2( + name = "/icons/min-v1.png", + sha256 = "74758e480ae76297c8947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + address = "https://min-v1.org", + description = mapOf(LOCALE to "This is a repo with minimal data."), + ) + + const val packageName = "org.fdroid.min1" + val version = PackageVersionV2( + added = 0, + file = FileV1( + name = "/${packageName}_23.apk", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1337, + ), + manifest = ManifestV2( + versionCode = 1, + versionName = "0", + ), + ) + val app = PackageV2( + metadata = MetadataV2( + added = 0, + lastUpdated = 0, + license = "", // not really needed, but easier for v1 conversion testing + ), + versions = mapOf( + version.file.sha256 to version, + ), + ) + val packages = mapOf(packageName to app) + + val index = IndexV2( + repo = repo, + packages = packages, + ) +} + +internal object TestDataMidV2 { + + val repo = RepoV2( + timestamp = 1337, + name = "MidV1", + icon = FileV2( + name = "/icons/mid-v1.png", + sha256 = "74758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 234232352235, + ), + address = "https://mid-v1.org", + description = mapOf( + LOCALE to "This is a repo with medium data.", + "de" to "Dies ist ein Repo mit mittlerer Datendichte.", + ), + mirrors = listOf(MirrorV2("https://mid-v1.com")), + categories = mapOf( + "Cat1" to CategoryV2( + name = mapOf(LOCALE to "Cat1"), + icon = FileV2( + name = "/icons/cat2.png", + sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = Long.MAX_VALUE, + ), + ), + "System" to CategoryV2( + name = emptyMap(), + ), + ), + antiFeatures = mapOf( + "AntiFeature" to AntiFeatureV2( + name = mapOf(LOCALE to "AntiFeature"), + description = mapOf(LOCALE to "A bad anti-feature, we can't show to users."), + ) + ) + ) + val repoCompat = repo.v1compat() + + const val packageName1 = TestDataMinV1.packageName + const val packageName2 = "org.fdroid.fdroid" + val version1_1 = PackageVersionV2( + added = 2342, + file = FileV1( + name = "/${packageName1}_23_2.apk", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1338, + ), + src = FileV2( + name = "/${packageName1}_23_2.zip", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1338, + ), + manifest = ManifestV2( + versionCode = 1, + versionName = "1", + usesPermission = listOf(PermissionV2("perm")), + nativeCode = listOf("x86"), + features = listOf(FeatureV2("feature")), + ), + antiFeatures = mapOf("AntiFeature" to mapOf(LOCALE to "reason")), + ) + val version1_1Compat = version1_1.v1compat() + val version1_2 = PackageVersionV2( + added = 0, + file = FileV1( + name = "/${packageName1}_42.apk", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1337, + ), + manifest = ManifestV2( + versionCode = 24, + versionName = "24", + usesSdk = UsesSdkV2( + minSdkVersion = 21, + targetSdkVersion = 32, + ), + maxSdkVersion = 4568, + signer = SignatureV2( + sha256 = listOf("824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf"), + ), + ), + src = FileV2( + name = "/${packageName1}_42.zip", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1338, + ), + antiFeatures = mapOf("AntiFeature" to mapOf(LOCALE to "reason")), + releaseChannels = listOf("Beta") + ) + val version1_2Compat = version1_2.v1compat().copy( + antiFeatures = mapOf("AntiFeature" to emptyMap()), + ) + + val app1 = PackageV2( + metadata = MetadataV2( + added = 1234567890, + categories = listOf(TestDataMidV1.categories[0]), + summary = mapOf( + LOCALE to "App1 summary", + "de" to "App1 Zusammenfassung", + ), + description = mapOf( + LOCALE to "App1 description", + "de" to "App1 beschreibung", + ), + name = mapOf( + LOCALE to "App1", + "de" to "app 1 name", + ), + author = Author(name = "App1 author"), + license = "GPLv3", + webSite = "http://min1.test.org", + icon = mapOf( + LOCALE to FileV2( + name = "/icons/icon-min1.png", + sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1337, + ), + ), + lastUpdated = 1234567891, + ), + versions = mapOf( + version1_1.file.sha256 to version1_1, + version1_2.file.sha256 to version1_2, + ), + ) + val app1Compat = app1.v1compat(true).copy( + versions = mapOf( + version1_1.file.sha256 to version1_1Compat, + version1_2.file.sha256 to version1_2Compat, + ), + ) + + val version2_1 = PackageVersionV2( + added = 1643250075000, + file = FileV1( + name = "/org.fdroid.fdroid_1014050.apk", + sha256 = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + size = 8165518, + ), + manifest = ManifestV2( + versionCode = 1014050, + versionName = "1.14", + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + signer = SignatureV2( + sha256 = listOf("43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab"), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.INTERNET"), + PermissionV2(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV2(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV2(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV2(name = "android.permission.BLUETOOTH"), + PermissionV2(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV2(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_SETTINGS"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION") + ), + ), + src = FileV2( + name = "/org.fdroid.fdroid_1014050_src.tar.gz", + sha256 = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d07", + size = 8165519, + ), + whatsNew = mapOf( + LOCALE to "* Overhaul Share menu to use built-in options like Nearby", + "eo" to "• rekonstruita menuo “Kunhavigi” por uzi enkonstruitajn eblojn, ekz.", + ), + ) + val version2_1Compat = version2_1.v1compat() + + val version2_2 = PackageVersionV2( + added = 1642785071000, + file = FileV1( + name = "/org.fdroid.fdroid_1014005.apk", + sha256 = "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d", + size = 8382606, + ), + manifest = ManifestV2( + versionCode = 1014005, + versionName = "1.14-alpha5", + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + signer = SignatureV2( + sha256 = listOf("43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab"), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.INTERNET"), + PermissionV2(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV2(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV2(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV2(name = "android.permission.BLUETOOTH"), + PermissionV2(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV2(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_SETTINGS"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION") + ), + nativeCode = listOf("fakeNativeCode"), + features = listOf(FeatureV2("fake feature")), + ), + src = FileV2( + name = "/org.fdroid.fdroid_1014005_src.tar.gz", + sha256 = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d07", + size = 8165519, + ), + antiFeatures = mapOf("FakeAntiFeature" to emptyMap()), + ) + val version2_2Compat = version2_2.v1compat() + + val version2_3 = PackageVersionV2( + added = 1635169849000, + file = FileV1( + name = "/org.fdroid.fdroid_1014003.apk", + sha256 = "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3", + size = 8276110, + ), + manifest = ManifestV2( + versionCode = 1014003, + versionName = "1.14-alpha3", + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + signer = SignatureV2( + sha256 = listOf("43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab"), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.INTERNET"), + PermissionV2(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV2(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV2(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV2(name = "android.permission.BLUETOOTH"), + PermissionV2(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV2(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_SETTINGS"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION") + ), + ), + src = FileV2( + name = "/org.fdroid.fdroid_1014003_src.tar.gz", + sha256 = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + size = 8165519, + ), + ) + val version2_3Compat = version2_3.v1compat() + + val version2_4 = PackageVersionV2( + added = 1632281731000, + file = FileV1( + name = "/org.fdroid.fdroid_1014002.apk", + sha256 = "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1", + size = 8284386, + ), + manifest = ManifestV2( + versionCode = 1014002, + versionName = "1.14-alpha2", + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + signer = SignatureV2( + sha256 = listOf("43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab"), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.INTERNET"), + PermissionV2(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV2(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV2(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV2(name = "android.permission.BLUETOOTH"), + PermissionV2(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV2(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_SETTINGS"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION") + ), + ), + src = FileV2( + name = "/org.fdroid.fdroid_1014002_src.tar.gz", + sha256 = "7c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + size = 7165519, + ), + ) + val version2_4Compat = version2_4.v1compat() + + val version2_5 = PackageVersionV2( + added = 1632281729000, + file = FileV1( + name = "/org.fdroid.fdroid_1014001.apk", + sha256 = "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610", + size = 8272166, + ), + manifest = ManifestV2( + versionCode = 1014001, + versionName = "1.14-alpha1", + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + signer = SignatureV2( + sha256 = listOf("43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab"), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.INTERNET"), + PermissionV2(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV2(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV2(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV2(name = "android.permission.BLUETOOTH"), + PermissionV2(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV2(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_SETTINGS"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE") + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION") + ), + ), + src = FileV2( + name = "/org.fdroid.fdroid_1014001_src.tar.gz", + sha256 = "6c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + size = 6165519, + ), + ) + val version2_5Compat = version2_5.v1compat() + + val app2 = PackageV2( + metadata = MetadataV2( + added = 1295222400000, + categories = listOf("System"), + name = mapOf( + "af" to "-درويد", + "be" to "F-Droid", + "bn" to "এফ-ড্রয়েড", + "ca" to "F-Droid", + "cs" to "F-Droid", + "cy" to "F-Droid", + "el" to "F-Droid", + "en-US" to "F-Droid", + "eo" to "F-Droid", + ), + summary = mapOf( + "af" to "متجر التطبيقات الذي يحترم الحرية والخصوصية)", + "be" to "Крама праграм, якая паважае свабоду і прыватнасць", + "bg" to "Магазинът за приложения, който уважава независимостта и поверителността", + "bn" to "যে অ্যাপ স্টোর স্বাধীনতা ও গোপনীয়তা সম্মান করে", + "bo" to "རང་དབང་དང་གསང་དོན་ལ་གུས་བརྩི་ཞུས་མཁན་གྱི་མཉེན་ཆས་ཉར་ཚགས་ཁང་།", + "ca" to "La botiga d'aplicacions que respecta la llibertat i la privacitat", + "cs" to "Zdroj aplikací který respektuje vaši svobodu a soukromí", + "cy" to "Yr ystorfa apiau sy'n parchu rhyddid a phreifatrwydd", + "de" to "Der App-Store, der Freiheit und Privatsphäre respektiert", + "el" to "Το κατάστημα εφαρμογών που σέβεται την ελευθερία και την ιδιωτικότητα", + "en-US" to "The app store that respects freedom and privacy", + "eo" to "Aplikaĵa vendejo respektanta liberecon kaj privatecon", + ), + description = mapOf( + "af" to "F-Droid is 'n installeerbare katalogus van gratis sagteware", + "bo" to "ཨེཕ་རོཌ་ནི་ཨེན་ཀྲོཌ་བབ་སྟེགས་ཀྱི་ཆེད་དུ་FOSS", + "ca" to "F-Droid és un catàleg instal·lable d'aplicacions de software lliure", + "cs" to "F-Droid je instalovatelný katalog softwarových libre", + "cy" to "Mae F-Droid yn gatalog y gellir ei osod o apiau meddalwedd " + + "rhyddar gyfer Android.", + "de" to "F-Droid ist ein installierbarer Katalog mit Libre SoftwareAndroid-Apps.", + "el" to "Το F-Droid είναι ένας κατάλογος εφαρμογών ελεύθερου λογισμικού", + "en-US" to "F-Droid is an installable catalogue of libre software", + "eo" to "F-Droid estas instalebla katalogo de liberaj aplikaĵoj por Android." + ), + changelog = "https://gitlab.com/fdroid/fdroidclient/raw/HEAD/CHANGELOG.md", + translation = "https://hosted.weblate.org/projects/f-droid/f-droid", + issueTracker = "https://gitlab.com/fdroid/fdroidclient/issues", + sourceCode = "https://gitlab.com/fdroid/fdroidclient", + donation = Donation( + url = "https://f-droid.org/donate", + liberapayID = "27859", + openCollective = "F-Droid-Euro", + flattrID = "343053", + ), + preferredSigner = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + license = "GPL-3.0-or-later", + webSite = "https://f-droid.org", + icon = mapOf( + LOCALE to FileV2( + name = "/icons/org.fdroid.fdroid.1014050.png", + sha256 = "224a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 1237, + ), + ), + featureGraphic = mapOf( + LOCALE to FileV2( + name = "/org.fdroid.fdroid/en-US/" + + "featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + sha256 = "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + size = 4237, + ), + ), + screenshots = Screenshots( + phone = mapOf( + LOCALE to listOf( + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-app-details.png", + sha256 = "424a109b2352138c3699760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 4237, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-dark-details.png", + sha256 = "424a109b2352138c3699760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 44287, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-dark-home.png", + sha256 = "424a109b2352138c3699760e1673385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 4587, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-dark-knownvuln.png", + sha256 = "424a109b2352138c3699760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 445837, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-knownvuln.png", + sha256 = "424a109b2352138c4599760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 4287, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-search.png", + sha256 = "424a109b2352138c3694760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 2857, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-updates.png", + sha256 = "424a109b2352138c3699750e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 485287, + ), + ), + ), + ), + lastUpdated = 1643250075000, + ), + versions = mapOf( + version2_1.file.sha256 to version2_1, + version2_2.file.sha256 to version2_2, + version2_3.file.sha256 to version2_3, + version2_4.file.sha256 to version2_4, + version2_5.file.sha256 to version2_5, + ), + ) + val app2Compat = app2.v1compat().copy( + versions = mapOf( + version2_1.file.sha256 to version2_1Compat, + version2_2.file.sha256 to version2_2Compat, + version2_3.file.sha256 to version2_3Compat, + version2_4.file.sha256 to version2_4Compat, + version2_5.file.sha256 to version2_5Compat, + ), + ) + val packages = mapOf(packageName1 to app1, packageName2 to app2) + + val index = IndexV2( + repo = repo, + packages = packages, + ) + val indexCompat = index.copy( + repo = repoCompat, + packages = mapOf( + packageName1 to app1Compat, + packageName2 to app2Compat, + ), + ) +} + +internal object TestDataMaxV2 { + + val repo = RepoV2( + timestamp = Long.MAX_VALUE, + name = "MaxV1", + icon = FileV2( + name = "/icons/max-v1.png", + sha256 = "14758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = Long.MAX_VALUE, + ), + address = "https://max-v1.org", + webBaseUrl = "https://www.max-v1.org", + description = mapOf( + LOCALE to "This is a repo with maximum data.", + "de" to "Dies ist ein Repo mit maximaler Datendichte.", + ), + mirrors = listOf( + MirrorV2("https://max-v1.com", "us"), + MirrorV2("https://max-v1.org", "nl"), + ), + antiFeatures = mapOf( + "VeryBad" to AntiFeatureV2( + name = emptyMap(), + ), + "Dont,Show,This" to AntiFeatureV2( + name = emptyMap(), + ), + "NotNice" to AntiFeatureV2( + name = emptyMap(), + ), + "AntiFeature" to AntiFeatureV2( + icon = FileV2( + name = "/icons/antifeature.png", + sha256 = "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 254916, + ), + name = mapOf(LOCALE to "AntiFeature"), + description = mapOf(LOCALE to "A bad anti-feature, we can't show to users."), + ), + "NonFreeNet" to AntiFeatureV2( + name = mapOf(LOCALE to "NonFreeNet"), + ), + "AddOne" to AntiFeatureV2( + name = mapOf(LOCALE to "AddOne anti feature"), + description = mapOf(LOCALE to "A bad anti-feature, that was added in an update."), + ), + ), + categories = mapOf( + "Cat3" to CategoryV2( + name = mapOf(LOCALE to "Cat3"), + icon = FileV2( + name = "/icons/cat3.png", + sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = Long.MAX_VALUE, + ), + description = mapOf(LOCALE to "Cat3"), + ), + "Cat2" to CategoryV2( + name = mapOf(LOCALE to "Cat3"), + icon = FileV2( + name = "/icons/cat2.png", + sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = Long.MAX_VALUE, + ), + description = mapOf(LOCALE to "Cat3"), + ), + "Cat1" to CategoryV2( + name = mapOf(LOCALE to "Cat1"), + icon = FileV2( + name = "/icons/cat1.png", + sha256 = "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = Long.MAX_VALUE, + ), + description = mapOf(LOCALE to "Cat1"), + ), + "NoMoreSystem" to CategoryV2( + name = emptyMap(), + ), + "OneMore" to CategoryV2( + name = emptyMap(), + ), + ), + releaseChannels = mapOf( + "Alpha" to ReleaseChannelV2( + name = mapOf("de" to "channel name alpha"), + description = mapOf("de-DE" to "channel desc alpha"), + ), + "Beta" to ReleaseChannelV2( + name = mapOf(LOCALE to "channel name"), + description = mapOf(LOCALE to "channel desc"), + ), + ) + ) + val repoCompat = repo.v1compat().copy( + description = mapOf(LOCALE to "This is a repo with medium data."), + categories = mapOf( + "Cat1" to CategoryV2(name = emptyMap()), + "System" to CategoryV2(name = emptyMap()), + ), + antiFeatures = mapOf( + "AntiFeature" to AntiFeatureV2(name = emptyMap()) + ), + ) + + const val packageName1 = TestDataMidV2.packageName1 + const val packageName2 = TestDataMidV2.packageName2 + const val packageName3 = "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moo" + + "dahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5Ung" + + "ohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raeph" + + "oowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y" + + val version2_2 = TestDataMidV2.version2_2.copy( + manifest = TestDataMidV2.version2_2.manifest.copy( + usesPermission = emptyList(), + usesPermissionSdk23 = emptyList(), + nativeCode = emptyList(), + features = emptyList(), + ), + whatsNew = mapOf( + "ch" to "This is new!", + ), + antiFeatures = mapOf( + "AddOne" to mapOf(LOCALE to "was added this update"), + ), + ) + val version2_3 = TestDataMidV2.version2_3.copy( + manifest = TestDataMidV2.version2_3.manifest.copy( + versionCode = 1014003, + versionName = "1.14-alpha3", + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + signer = SignatureV2( + sha256 = listOf("43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab"), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.ACCESS_MEDIA"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE", maxSdkVersion = 32), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.READ_MY_ASS"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE"), + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_FINE_LOCATION"), + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION", maxSdkVersion = 3), + ), + ), + file = TestDataMidV2.version2_3.file.copy( + size = 8276110, + ), + src = TestDataMidV2.version2_3.src?.copy( + name = "/org.fdroid.fdroid_1014003_src.tar.gz", + ), + antiFeatures = mapOf( + "AddOne" to mapOf(LOCALE to "was added this update"), + ), + ) + val version2_4 = TestDataMidV2.version2_4.copy( + antiFeatures = mapOf( + "AddOne" to emptyMap(), + ), + ) + val version2_5 = TestDataMidV2.version2_5.copy( + antiFeatures = mapOf( + "AddOne" to mapOf(LOCALE to "was added this update"), + ), + ) + val app2 = TestDataMidV2.app2.copy( + metadata = TestDataMidV2.app2.metadata.copy( + categories = listOf("NoMoreSystem", "OneMore"), + summary = mapOf( + LOCALE to "new summary in en-US", + "ch" to "new summary", + "de" to "Der App-Store, der Freiheit und Privatsphäre respektiert", + ), + description = mapOf( + LOCALE to "F-Droid is an installable catalogue of libre software", + "ch" to "new desc", + ), + webSite = "https://fdroid.org", + name = mapOf( + LOCALE to "F-DroidX", + "ch" to "new name", + ), + icon = mapOf( + LOCALE to FileV2( + name = "/org.fdroid.fdroid/en-US/new icon", + sha256 = "324a109b2352138c3699760e1683385d1ed50ce526fc7982f8d65757743374ba", + size = 2233, + ) + ), + screenshots = TestDataMidV2.app2.metadata.screenshots?.copy( + phone = mapOf( + LOCALE to listOf( + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-app-details.png", + sha256 = "424a109b2352138c3699760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 4237, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-dark-details.png", + sha256 = "424a109b2352138c3699760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 44287, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-dark-home.png", + sha256 = "424a109b2352138c3699760e1673385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 4587, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-search.png", + sha256 = "424a109b2352138c3694760e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 2857, + ), + FileV2( + name = "/org.fdroid.fdroid/en-US/phoneScreenshots/" + + "screenshot-updates.png", + sha256 = "424a109b2352138c3699750e1683385d" + + "0ed50ce526fc7982f8d65757743374bf", + size = 485286, + ), + ), + ), + tenInch = mapOf( + "ch" to listOf( + FileV2( + name = "/org.fdroid.fdroid/ch/tenInchScreenshots/new screenshots", + sha256 = "54758e380ae76297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = Long.MIN_VALUE, + ) + ), + ), + wear = null, + ), + ), + versions = mapOf( + // remove one and replace two + version2_2.file.sha256 to version2_2, + version2_3.file.sha256 to version2_3, + version2_4.file.sha256 to version2_4, + version2_5.file.sha256 to version2_5, + ), + ) + private val app2CompatPre = app2.v1compat(true) + val app2Compat = app2CompatPre.copy( + // due to locale overrides + metadata = app2CompatPre.metadata.copy( + summary = mapOf(LOCALE to "new summary"), + description = mapOf(LOCALE to "new description"), + ), + versions = mapOf( + // remove one and replace two + version2_2.file.sha256 to version2_2.v1compat(), + version2_3.file.sha256 to version2_3.v1compat(), + version2_4.file.sha256 to version2_4.v1compat(), + version2_5.file.sha256 to version2_5.v1compat(), + ), + ) + + val version3_1 = PackageVersionV2( + added = 1643250075000, + file = FileV1( + name = "/org.fdroid.fdroid_1014050.apk", + sha256 = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + size = 8165518, + ), + src = FileV2( + name = "/org.fdroid.fdroid_1014050_src.tar.gz", + sha256 = "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + size = 8165518, + ), + manifest = ManifestV2( + versionName = "1.14", + versionCode = 1014050, + usesSdk = UsesSdkV2( + minSdkVersion = 22, + targetSdkVersion = 25, + ), + maxSdkVersion = Int.MAX_VALUE, + signer = SignatureV2( + sha256 = listOf( + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "33238d512c1e3eb2d6569f4a3bfbf5523418b22e0a3ed1552770abb9a9c9ccvb", + ), + ), + usesPermission = listOf( + PermissionV2(name = "android.permission.INTERNET"), + PermissionV2(name = "android.permission.ACCESS_NETWORK_STATE"), + PermissionV2(name = "android.permission.ACCESS_WIFI_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_MULTICAST_STATE"), + PermissionV2(name = "android.permission.CHANGE_NETWORK_STATE"), + PermissionV2(name = "android.permission.CHANGE_WIFI_STATE"), + PermissionV2(name = "android.permission.BLUETOOTH"), + PermissionV2(name = "android.permission.BLUETOOTH_ADMIN"), + PermissionV2(name = "android.permission.RECEIVE_BOOT_COMPLETED"), + PermissionV2(name = "android.permission.READ_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_EXTERNAL_STORAGE"), + PermissionV2(name = "android.permission.WRITE_SETTINGS"), + PermissionV2(name = "android.permission.NFC"), + PermissionV2(name = "android.permission.USB_PERMISSION", maxSdkVersion = 22), + PermissionV2(name = "android.permission.WAKE_LOCK"), + PermissionV2(name = "android.permission.FOREGROUND_SERVICE"), + ), + usesPermissionSdk23 = listOf( + PermissionV2(name = "android.permission.ACCESS_COARSE_LOCATION"), + PermissionV2( + name = "android.permission.USB_PERMISSION", + maxSdkVersion = Int.MAX_VALUE, + ), + ), + nativeCode = listOf("x86", "x86_64"), + features = listOf(FeatureV2("feature"), FeatureV2("feature2")), + ), + releaseChannels = listOf("Beta", "Alpha"), + antiFeatures = mapOf( + "AntiFeature" to emptyMap(), + "NonFreeNet" to emptyMap(), + "NotNice" to emptyMap(), + "VeryBad" to emptyMap(), + "Dont,Show,This" to emptyMap(), + "anti-feature" to mapOf(LOCALE to "bla", "de" to "blubb"), + "anti-feature2" to mapOf("de" to "blabla"), + ), + whatsNew = mapOf( + LOCALE to "this is new", + "de" to "das ist neu", + ), + ) + val app3 = PackageV2( + metadata = MetadataV2( + name = mapOf( + LOCALE to "App3", + "en" to "en ", + ), + summary = mapOf( + LOCALE to "App3 summary", + "en" to "en ", + ), + description = mapOf( + LOCALE to "App3 description", + "en" to "en ", + ), + added = 1234567890, + lastUpdated = Long.MAX_VALUE, + webSite = "http://min1.test.org", + changelog = "changeLog3", + license = "GPLv3", + sourceCode = "source code3", + issueTracker = "tracker3", + translation = "translation3", + preferredSigner = "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + categories = listOf("Cat1", "Cat2", "Cat3"), + author = Author( + name = "App3 author", + email = "email", + website = "website", + phone = "phone", + ), + donation = Donation( + url = "donate", + liberapayID = "liberapayID", + liberapay = "liberapay", + openCollective = "openCollective", + bitcoin = "bitcoin", + litecoin = "litecoin", + flattrID = "flattrID", + ), + icon = mapOf( + "en" to FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahl" + + "onu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaegh" + + "e8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGha" + + "hm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + sha256 = "32758e380aeg6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 2253245, + ), + ), + featureGraphic = mapOf( + "en" to FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahl" + + "onu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaegh" + + "e8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGha" + + "hm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + sha256 = "54758e380ae762f7c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 32453245, + ), + ), + promoGraphic = mapOf( + "en" to FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahl" + + "onu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaegh" + + "e8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGha" + + "hm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + sha256 = "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 4523325, + ), + ), + tvBanner = mapOf( + "en" to FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahl" + + "onu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaegh" + + "e8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGha" + + "hm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + sha256 = "54758e380aeh6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + size = 32453245, + ), + ), + video = mapOf( + "en" to "en ", + ), + screenshots = Screenshots( + phone = mapOf( + "en" to listOf( + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/phoneScreenshots/en phoneScreenshots", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/phoneScreenshots/en phoneScreenshots2", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + ) + ), + sevenInch = mapOf( + "en" to listOf( + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/sevenInchScreenshots/en sevenInchScreenshots", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/sevenInchScreenshots/en sevenInchScreenshots2", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + ), + ), + tenInch = mapOf( + "en" to listOf( + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/tenInchScreenshots/en tenInchScreenshots", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/tenInchScreenshots/en tenInchScreenshots2", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + ), + ), + wear = mapOf( + "en" to listOf( + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/wearScreenshots/en wearScreenshots", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/wearScreenshots/en wearScreenshots2", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0, + ), + ), + ), + tv = mapOf( + "en" to listOf( + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/tvScreenshots/en tvScreenshots", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0), + FileV2( + name = "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev" + + "8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo" + + "5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6r" + + "aephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8" + + "tu4Y/en/tvScreenshots/en tvScreenshots2", + sha256 = "54758e380aee6297c7947f107db9ea03" + + "d2933c9d5c110d02046977cf78d43def", + size = 0), + ), + ), + ), + ), + versions = mapOf( + version3_1.file.sha256 to version3_1, + ), + ) + val app3Compat = app3.v1compat(true).copy( + versions = mapOf( + version3_1.file.sha256 to version3_1.v1compat(), + ), + ) + + val index = IndexV2( + repo = repo, + packages = mapOf( + TestDataMidV2.packageName1 to TestDataMidV2.app1, + TestDataMidV2.packageName2 to app2, + packageName3 to app3, + ), + ) + val indexCompat = index.v1compat().copy( + packages = mapOf( + TestDataMidV2.packageName1 to TestDataMidV2.app1Compat, + TestDataMidV2.packageName2 to app2Compat, + packageName3 to app3Compat, + ), + ) +} diff --git a/index/src/sharedTest/kotlin/org/fdroid/test/TestUtils.kt b/index/src/sharedTest/kotlin/org/fdroid/test/TestUtils.kt index 88544fc92..527bd16fd 100644 --- a/index/src/sharedTest/kotlin/org/fdroid/test/TestUtils.kt +++ b/index/src/sharedTest/kotlin/org/fdroid/test/TestUtils.kt @@ -1,5 +1,6 @@ package org.fdroid.test +import org.fdroid.index.v2.IndexV2 import kotlin.random.Random public object TestUtils { @@ -34,4 +35,26 @@ public object TestUtils { return if (Random.nextBoolean()) null else this } + public fun IndexV2.sorted(): IndexV2 = copy( + packages = packages.toSortedMap().mapValues { entry -> + entry.value.copy( + metadata = entry.value.metadata.copy( + name = entry.value.metadata.name?.toSortedMap(), + summary = entry.value.metadata.summary?.toSortedMap(), + description = entry.value.metadata.description?.toSortedMap(), + icon = entry.value.metadata.icon?.toSortedMap(), + ), + versions = entry.value.versions.mapValues { + val pv = it.value + pv.copy( + manifest = pv.manifest.copy( + usesPermission = pv.manifest.usesPermission.sortedBy { p -> p.name }, + usesPermissionSdk23 = pv.manifest.usesPermissionSdk23.sortedBy { p -> p.name } + ) + ) + } + ) + } + ) + } diff --git a/index/src/sharedTest/resources/index-empty-v1.json b/index/src/sharedTest/resources/index-empty-v1.json new file mode 100644 index 000000000..25f0234d3 --- /dev/null +++ b/index/src/sharedTest/resources/index-empty-v1.json @@ -0,0 +1,10 @@ +{ + "repo": { + "timestamp": 23, + "version": 23, + "name": "EmptyV1", + "icon": "empty-v1.png", + "address": "https://empty-v1.org", + "description": "This is a repo with empty data." + } +} diff --git a/index/src/sharedTest/resources/index-empty-v2.json b/index/src/sharedTest/resources/index-empty-v2.json new file mode 100644 index 000000000..fb4cb7f90 --- /dev/null +++ b/index/src/sharedTest/resources/index-empty-v2.json @@ -0,0 +1,15 @@ +{ + "repo": { + "name": "EmptyV1", + "icon": { + "name": "/icons/empty-v1.png", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 32492 + }, + "address": "https://empty-v1.org", + "description": { + "en-US": "This is a repo with empty data." + }, + "timestamp": 23 + } +} diff --git a/index/src/sharedTest/resources/index-max-v1.json b/index/src/sharedTest/resources/index-max-v1.json new file mode 100644 index 000000000..467542cca --- /dev/null +++ b/index/src/sharedTest/resources/index-max-v1.json @@ -0,0 +1,600 @@ +{ + "repo": { + "timestamp": 9223372036854775807, + "version": 2147483647, + "maxage": 2147483647, + "name": "MaxV1", + "icon": "max-v1.png", + "address": "https://max-v1.org", + "description": "This is a repo with maximum data.", + "mirrors": [ + "https://max-v1.com", + "https://max-v1.org" + ] + }, + "requests": { + "install": [ + "installThis", + "installThat" + ], + "uninstall": [ + "uninstallThis" + ] + }, + "apps": [ + { + "categories": [ + "Cat1" + ], + "antiFeatures": [ + "AntiFeature" + ], + "summary": "App1 summary", + "description": "App1 description", + "name": "App1", + "authorName": "App1 author", + "license": "GPLv3", + "webSite": "http://min1.test.org", + "added": 1234567890, + "icon": "icon-min1.png", + "packageName": "org.fdroid.min1", + "lastUpdated": 1234567891, + "localized": { + "de": { + "description": "App1 beschreibung", + "name": "app 1 name", + "summary": "App1 Zusammenfassung" + } + } + }, + { + "categories": [ + "NoMoreSystem", + "OneMore" + ], + "antiFeatures": [ + "AddOne" + ], + "summary": "new summary", + "description": "new description", + "changelog": "https://gitlab.com/fdroid/fdroidclient/raw/HEAD/CHANGELOG.md", + "translation": "https://hosted.weblate.org/projects/f-droid/f-droid", + "issueTracker": "https://gitlab.com/fdroid/fdroidclient/issues", + "sourceCode": "https://gitlab.com/fdroid/fdroidclient", + "binaries": "https://fdroid.org/binaries", + "name": "F-DroidX", + "donate": "https://f-droid.org/donate", + "liberapayID": "27859", + "openCollective": "F-Droid-Euro", + "flattrID": "343053", + "suggestedVersionName": "1.14", + "suggestedVersionCode": "1014050", + "license": "GPL-3.0-or-later", + "webSite": "https://fdroid.org", + "added": 1295222400000, + "icon": "org.fdroid.fdroid.1014050.png", + "packageName": "org.fdroid.fdroid", + "lastUpdated": 1643250075000, + "localized": { + "ch": { + "description": "new desc", + "name": "new name", + "whatsNew": "This is new!", + "tenInchScreenshots": [ + "new screenshots" + ], + "summary": "new summary" + }, + "de": { + "summary": "Der App-Store, der Freiheit und Privatsphäre respektiert" + }, + "en-US": { + "description": "F-Droid is an installable catalogue of libre software", + "icon": "new icon", + "phoneScreenshots": [ + "screenshot-app-details.png", + "screenshot-dark-details.png", + "screenshot-dark-home.png", + "screenshot-search.png", + "screenshot-updates.png" + ], + "featureGraphic": "featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + "summary": "new summary in en-US" + } + } + }, + { + "categories": [ + "Cat1", + "Cat2", + "Cat3" + ], + "antiFeatures": [ + "AntiFeature", + "NonFreeNet", + "NotNice", + "VeryBad", + "Dont,Show,This" + ], + "summary": "App3 summary", + "description": "App3 description", + "changelog": "changeLog3", + "translation": "translation3", + "issueTracker": "tracker3", + "sourceCode": "source code3", + "binaries": "binaries3", + "name": "App3", + "authorName": "App3 author", + "authorEmail": "email", + "authorWebSite": "website", + "authorPhone": "phone", + "donate": "donate", + "liberapayID": "liberapayID", + "liberapay": "liberapay", + "openCollective": "openCollective", + "bitcoin": "bitcoin", + "litecoin": "litecoin", + "flattrID": "flattrID", + "suggestedVersionName": "1.0", + "suggestedVersionCode": "-9223372036854775808", + "license": "GPLv3", + "webSite": "http://min1.test.org", + "added": 1234567890, + "icon": "icon-max1.png", + "packageName": "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y", + "lastUpdated": 9223372036854775807, + "localized": { + "en-US": { + "whatsNew": "this is new" + }, + "de": { + "whatsNew": "das ist neu" + }, + "en": { + "description": "en ", + "name": "en ", + "icon": "en ", + "video": "en ", + "phoneScreenshots": [ + "en phoneScreenshots", + "en phoneScreenshots2" + ], + "sevenInchScreenshots": [ + "en sevenInchScreenshots", + "en sevenInchScreenshots2" + ], + "tenInchScreenshots": [ + "en tenInchScreenshots", + "en tenInchScreenshots2" + ], + "wearScreenshots": [ + "en wearScreenshots", + "en wearScreenshots2" + ], + "tvScreenshots": [ + "en tvScreenshots", + "en tvScreenshots2" + ], + "featureGraphic": "en ", + "promoGraphic": "en ", + "tvBanner": "en ", + "summary": "en " + } + }, + "allowedAPKSigningKeys": [ + "key1, key2" + ] + } + ], + "packages": { + "org.fdroid.min1": [ + { + "added": 2342, + "apkName": "org.fdroid.min1_23_2.apk", + "hash": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "hashType": "sha256", + "packageName": "org.fdroid.min1", + "size": 1338, + "srcname": "org.fdroid.min1_23_2.zip", + "uses-permission": [ + [ + "perm", + null + ] + ], + "versionCode": 1, + "versionName": "1", + "nativecode": [ + "x86" + ], + "features": [ + "feature" + ], + "antiFeatures": [ + "anti-feature" + ] + }, + { + "apkName": "org.fdroid.min1_42.apk", + "hash": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "hashType": "sha256", + "minSdkVersion": 21, + "maxSdkVersion": 4568, + "targetSdkVersion": 32, + "packageName": "org.fdroid.min1", + "sig": "old", + "signer": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337, + "srcname": "org.fdroid.min1_42.zip", + "versionCode": 24, + "versionName": "24" + } + ], + "org.fdroid.fdroid": [ + { + "added": 1642785071000, + "apkName": "org.fdroid.fdroid_1014005.apk", + "hash": "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8382606, + "srcname": "org.fdroid.fdroid_1014005_src.tar.gz", + "versionCode": 1014005, + "versionName": "1.14-alpha5", + "nativecode": [ + ], + "features": [ + ], + "antiFeatures": [ + ] + }, + { + "added": 1635169849000, + "apkName": "org.fdroid.fdroid_1014003.apk", + "hash": "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8276110, + "srcname": "org.fdroid.fdroid_1014003_src.tar.gz", + "uses-permission": [ + [ + "android.permission.ACCESS_MEDIA", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + 32 + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.READ_MY_ASS", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_FINE_LOCATION", + null + ], + [ + "android.permission.ACCESS_COARSE_LOCATION", + 3 + ] + ], + "versionCode": 1014003, + "versionName": "1.14-alpha3" + }, + { + "added": 1632281731000, + "apkName": "org.fdroid.fdroid_1014002.apk", + "hash": "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8284386, + "srcname": "org.fdroid.fdroid_1014002_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014002, + "versionName": "1.14-alpha2" + }, + { + "added": 1632281729000, + "apkName": "org.fdroid.fdroid_1014001.apk", + "hash": "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8272166, + "srcname": "org.fdroid.fdroid_1014001_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014001, + "versionName": "1.14-alpha1" + } + ], + "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y": [ + { + "added": 1643250075000, + "apkName": "org.fdroid.fdroid_1014050.apk", + "hash": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + "hashType": "sha256", + "minSdkVersion": 22, + "maxSdkVersion": 2147483647, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8165518, + "srcname": "org.fdroid.fdroid_1014050_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ], + [ + "android.permission.USB_PERMISSION", + 2147483647 + ] + ], + "versionCode": 1014050, + "versionName": "1.14", + "nativecode": [ + "x86", + "x86_64" + ], + "features": [ + "feature", + "feature2" + ], + "antiFeatures": [ + "anti-feature", + "anti-feature2" + ] + } + ] + } +} diff --git a/index/src/sharedTest/resources/index-max-v2.json b/index/src/sharedTest/resources/index-max-v2.json new file mode 100644 index 000000000..d8f15f5d5 --- /dev/null +++ b/index/src/sharedTest/resources/index-max-v2.json @@ -0,0 +1,853 @@ +{ + "repo": { + "name": "MaxV1", + "icon": { + "name": "/icons/max-v1.png", + "sha256": "14758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 9223372036854775807 + }, + "address": "https://max-v1.org", + "webBaseUrl": "https://www.max-v1.org", + "description": { + "en-US": "This is a repo with maximum data.", + "de": "Dies ist ein Repo mit maximaler Datendichte." + }, + "mirrors": [ + { + "url": "https://max-v1.com", + "location": "us" + }, + { + "url": "https://max-v1.org", + "location": "nl" + } + ], + "timestamp": 9223372036854775807, + "antiFeatures": { + "VeryBad": { + "name": { + } + }, + "Dont,Show,This": { + "name": { + } + }, + "NotNice": { + "name": { + } + }, + "AntiFeature": { + "icon": { + "name": "/icons/antifeature.png", + "sha256": "24758e480ae66297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 254916 + }, + "name": { + "en-US": "AntiFeature" + }, + "description": { + "en-US": "A bad anti-feature, we can't show to users." + } + }, + "NonFreeNet": { + "name": { + "en-US": "NonFreeNet" + } + }, + "AddOne": { + "name": { + "en-US": "AddOne anti feature" + }, + "description": { + "en-US": "A bad anti-feature, that was added in an update." + } + } + }, + "categories": { + "Cat3": { + "icon": { + "name": "/icons/cat3.png", + "sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 9223372036854775807 + }, + "name": { + "en-US": "Cat3" + }, + "description": { + "en-US": "Cat3" + } + }, + "Cat2": { + "icon": { + "name": "/icons/cat2.png", + "sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 9223372036854775807 + }, + "name": { + "en-US": "Cat3" + }, + "description": { + "en-US": "Cat3" + } + }, + "Cat1": { + "icon": { + "name": "/icons/cat1.png", + "sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 9223372036854775807 + }, + "name": { + "en-US": "Cat1" + }, + "description": { + "en-US": "Cat1" + } + }, + "NoMoreSystem": { + "name": { + } + }, + "OneMore": { + "name": { + } + } + }, + "releaseChannels": { + "Alpha": { + "name": { + "de": "channel name alpha" + }, + "description": { + "de-DE": "channel desc alpha" + } + }, + "Beta": { + "name": { + "en-US": "channel name" + }, + "description": { + "en-US": "channel desc" + } + } + } + }, + "packages": { + "org.fdroid.min1": { + "metadata": { + "name": { + "en-US": "App1", + "de": "app 1 name" + }, + "summary": { + "en-US": "App1 summary", + "de": "App1 Zusammenfassung" + }, + "description": { + "en-US": "App1 description", + "de": "App1 beschreibung" + }, + "added": 1234567890, + "lastUpdated": 1234567891, + "webSite": "http://min1.test.org", + "license": "GPLv3", + "categories": [ + "Cat1" + ], + "author": { + "name": "App1 author" + }, + "icon": { + "en-US": { + "name": "/icons/icon-min1.png", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337 + } + } + }, + "versions": { + "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": { + "added": 0, + "file": { + "name": "/org.fdroid.min1_42.apk", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337 + }, + "src": { + "name": "/org.fdroid.min1_42.zip", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1338 + }, + "manifest": { + "versionName": "24", + "versionCode": 24, + "usesSdk": { + "minSdkVersion": 21, + "targetSdkVersion": 32 + }, + "maxSdkVersion": 4568, + "signer": { + "sha256": [ + "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf" + ] + } + }, + "releaseChannels": [ + "Beta" + ], + "antiFeatures": { + "AntiFeature": { + "en-US": "reason" + } + } + } + } + }, + "org.fdroid.fdroid": { + "metadata": { + "name": { + "en-US": "F-DroidX", + "ch": "new name" + }, + "summary": { + "en-US": "new summary in en-US", + "ch": "new summary", + "de": "Der App-Store, der Freiheit und Privatsphäre respektiert" + }, + "description": { + "en-US": "F-Droid is an installable catalogue of libre software", + "ch": "new desc" + }, + "added": 1295222400000, + "lastUpdated": 1643250075000, + "webSite": "https://fdroid.org", + "changelog": "https://gitlab.com/fdroid/fdroidclient/raw/HEAD/CHANGELOG.md", + "license": "GPL-3.0-or-later", + "sourceCode": "https://gitlab.com/fdroid/fdroidclient", + "issueTracker": "https://gitlab.com/fdroid/fdroidclient/issues", + "translation": "https://hosted.weblate.org/projects/f-droid/f-droid", + "preferredSigner": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "categories": [ + "NoMoreSystem", + "OneMore" + ], + "donation": { + "url": "https://f-droid.org/donate", + "liberapayID": "27859", + "openCollective": "F-Droid-Euro", + "flattrID": "343053" + }, + "icon": { + "en-US": { + "name": "/org.fdroid.fdroid/en-US/new icon", + "sha256": "324a109b2352138c3699760e1683385d1ed50ce526fc7982f8d65757743374ba", + "size": 2233 + } + }, + "featureGraphic": { + "en-US": { + "name": "/org.fdroid.fdroid/en-US/featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 4237 + } + }, + "screenshots": { + "phone": { + "en-US": [ + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-app-details.png", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 4237 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-dark-details.png", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 44287 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-dark-home.png", + "sha256": "424a109b2352138c3699760e1673385d0ed50ce526fc7982f8d65757743374bf", + "size": 4587 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-search.png", + "sha256": "424a109b2352138c3694760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 2857 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-updates.png", + "sha256": "424a109b2352138c3699750e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 485286 + } + ] + }, + "tenInch": { + "ch": [ + { + "name": "/org.fdroid.fdroid/ch/tenInchScreenshots/new screenshots", + "sha256": "54758e380ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": -9223372036854775808 + } + ] + } + } + }, + "versions": { + "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d": { + "added": 1642785071000, + "file": { + "name": "/org.fdroid.fdroid_1014005.apk", + "sha256": "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d", + "size": 8382606 + }, + "src": { + "name": "/org.fdroid.fdroid_1014005_src.tar.gz", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d07", + "size": 8165519 + }, + "manifest": { + "versionName": "1.14-alpha5", + "versionCode": 1014005, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + } + }, + "antiFeatures": { + "AddOne": { + "en-US": "was added this update" + } + }, + "whatsNew": { + "ch": "This is new!" + } + }, + "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3": { + "added": 1635169849000, + "file": { + "name": "/org.fdroid.fdroid_1014003.apk", + "sha256": "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3", + "size": 8276110 + }, + "src": { + "name": "/org.fdroid.fdroid_1014003_src.tar.gz", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + "size": 8165519 + }, + "manifest": { + "versionName": "1.14-alpha3", + "versionCode": 1014003, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.ACCESS_MEDIA" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE", + "maxSdkVersion": 32 + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.READ_MY_ASS" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_FINE_LOCATION" + }, + { + "name": "android.permission.ACCESS_COARSE_LOCATION", + "maxSdkVersion": 3 + } + ] + }, + "antiFeatures": { + "AddOne": { + "en-US": "was added this update" + } + } + }, + "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1": { + "added": 1632281731000, + "file": { + "name": "/org.fdroid.fdroid_1014002.apk", + "sha256": "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1", + "size": 8284386 + }, + "src": { + "name": "/org.fdroid.fdroid_1014002_src.tar.gz", + "sha256": "7c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + "size": 7165519 + }, + "manifest": { + "versionName": "1.14-alpha2", + "versionCode": 1014002, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ] + }, + "antiFeatures": { + "AddOne": { + } + } + }, + "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610": { + "added": 1632281729000, + "file": { + "name": "/org.fdroid.fdroid_1014001.apk", + "sha256": "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610", + "size": 8272166 + }, + "src": { + "name": "/org.fdroid.fdroid_1014001_src.tar.gz", + "sha256": "6c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + "size": 6165519 + }, + "manifest": { + "versionName": "1.14-alpha1", + "versionCode": 1014001, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ] + }, + "antiFeatures": { + "AddOne": { + "en-US": "was added this update" + } + } + } + } + }, + "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y": { + "metadata": { + "name": { + "en-US": "App3", + "en": "en " + }, + "summary": { + "en-US": "App3 summary", + "en": "en " + }, + "description": { + "en-US": "App3 description", + "en": "en " + }, + "added": 1234567890, + "lastUpdated": 9223372036854775807, + "webSite": "http://min1.test.org", + "changelog": "changeLog3", + "license": "GPLv3", + "sourceCode": "source code3", + "issueTracker": "tracker3", + "translation": "translation3", + "preferredSigner": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "categories": [ + "Cat1", + "Cat2", + "Cat3" + ], + "author": { + "name": "App3 author", + "email": "email", + "website": "website", + "phone": "phone" + }, + "donation": { + "url": "donate", + "liberapayID": "liberapayID", + "liberapay": "liberapay", + "openCollective": "openCollective", + "bitcoin": "bitcoin", + "litecoin": "litecoin", + "flattrID": "flattrID" + }, + "icon": { + "en": { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + "sha256": "32758e380aeg6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 2253245 + } + }, + "featureGraphic": { + "en": { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + "sha256": "54758e380ae762f7c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 32453245 + } + }, + "promoGraphic": { + "en": { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 4523325 + } + }, + "tvBanner": { + "en": { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/en ", + "sha256": "54758e380aeh6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 32453245 + } + }, + "video": { + "en": "en " + }, + "screenshots": { + "phone": { + "en": [ + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/phoneScreenshots/en phoneScreenshots", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/phoneScreenshots/en phoneScreenshots2", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + } + ] + }, + "sevenInch": { + "en": [ + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/sevenInchScreenshots/en sevenInchScreenshots", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/sevenInchScreenshots/en sevenInchScreenshots2", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + } + ] + }, + "tenInch": { + "en": [ + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/tenInchScreenshots/en tenInchScreenshots", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/tenInchScreenshots/en tenInchScreenshots2", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + } + ] + }, + "wear": { + "en": [ + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/wearScreenshots/en wearScreenshots", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/wearScreenshots/en wearScreenshots2", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + } + ] + }, + "tv": { + "en": [ + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/tvScreenshots/en tvScreenshots", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + { + "name": "/Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moodahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5UngohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raephoowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y/en/tvScreenshots/en tvScreenshots2", + "sha256": "54758e380aee6297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + } + ] + } + } + }, + "versions": { + "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06": { + "added": 1643250075000, + "file": { + "name": "/org.fdroid.fdroid_1014050.apk", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + "size": 8165518 + }, + "src": { + "name": "/org.fdroid.fdroid_1014050_src.tar.gz", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + "size": 8165518 + }, + "manifest": { + "versionName": "1.14", + "versionCode": 1014050, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "maxSdkVersion": 2147483647, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "33238d512c1e3eb2d6569f4a3bfbf5523418b22e0a3ed1552770abb9a9c9ccvb" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 2147483647 + } + ], + "nativecode": [ + "x86", + "x86_64" + ], + "features": [ + { + "name": "feature" + }, + { + "name": "feature2" + } + ] + }, + "releaseChannels": [ + "Beta", + "Alpha" + ], + "antiFeatures": { + "AntiFeature": { + }, + "NonFreeNet": { + }, + "NotNice": { + }, + "VeryBad": { + }, + "Dont,Show,This": { + }, + "anti-feature": { + "en-US": "bla", + "de": "blubb" + }, + "anti-feature2": { + "de": "blabla" + } + }, + "whatsNew": { + "en-US": "this is new", + "de": "das ist neu" + } + } + } + } + } +} diff --git a/index/src/sharedTest/resources/index-mid-v1.json b/index/src/sharedTest/resources/index-mid-v1.json new file mode 100644 index 000000000..f674331b8 --- /dev/null +++ b/index/src/sharedTest/resources/index-mid-v1.json @@ -0,0 +1,629 @@ +{ + "repo": { + "timestamp": 1337, + "version": 1, + "maxage": 23, + "name": "MidV1", + "icon": "mid-v1.png", + "address": "https://mid-v1.org", + "description": "This is a repo with medium data.", + "mirrors": [ + "https://mid-v1.com" + ] + }, + "requests": { + "install": [ + "installThis" + ], + "uninstall": [ + "uninstallThis" + ] + }, + "apps": [ + { + "categories": [ + "Cat1" + ], + "antiFeatures": [ + "AntiFeature" + ], + "summary": "App1 summary", + "description": "App1 description", + "name": "App1", + "authorName": "App1 author", + "license": "GPLv3", + "webSite": "http://min1.test.org", + "added": 1234567890, + "icon": "icon-min1.png", + "packageName": "org.fdroid.min1", + "lastUpdated": 1234567891, + "localized": { + "de": { + "description": "App1 beschreibung", + "name": "app 1 name", + "summary": "App1 Zusammenfassung" + } + } + }, + { + "categories": [ + "System" + ], + "changelog": "https://gitlab.com/fdroid/fdroidclient/raw/HEAD/CHANGELOG.md", + "translation": "https://hosted.weblate.org/projects/f-droid/f-droid", + "issueTracker": "https://gitlab.com/fdroid/fdroidclient/issues", + "sourceCode": "https://gitlab.com/fdroid/fdroidclient", + "donate": "https://f-droid.org/donate", + "liberapayID": "27859", + "openCollective": "F-Droid-Euro", + "flattrID": "343053", + "suggestedVersionName": "1.14", + "suggestedVersionCode": "1014050", + "license": "GPL-3.0-or-later", + "webSite": "https://f-droid.org", + "added": 1295222400000, + "icon": "org.fdroid.fdroid.1014050.png", + "packageName": "org.fdroid.fdroid", + "lastUpdated": 1643250075000, + "localized": { + "af": { + "description": "F-Droid is 'n installeerbare katalogus van gratis sagteware", + "name": "-درويد", + "summary": "متجر التطبيقات الذي يحترم الحرية والخصوصية)" + }, + "be": { + "name": "F-Droid", + "summary": "Крама праграм, якая паважае свабоду і прыватнасць" + }, + "bg": { + "summary": "Магазинът за приложения, който уважава независимостта и поверителността" + }, + "bn": { + "name": "এফ-ড্রয়েড", + "summary": "যে অ্যাপ স্টোর স্বাধীনতা ও গোপনীয়তা সম্মান করে" + }, + "bo": { + "description": "ཨེཕ་རོཌ་ནི་ཨེན་ཀྲོཌ་བབ་སྟེགས་ཀྱི་ཆེད་དུ་FOSS", + "summary": "རང་དབང་དང་གསང་དོན་ལ་གུས་བརྩི་ཞུས་མཁན་གྱི་མཉེན་ཆས་ཉར་ཚགས་ཁང་།" + }, + "ca": { + "description": "F-Droid és un catàleg instal·lable d'aplicacions de software lliure", + "name": "F-Droid", + "summary": "La botiga d'aplicacions que respecta la llibertat i la privacitat" + }, + "cs": { + "description": "F-Droid je instalovatelný katalog softwarových libre", + "name": "F-Droid", + "summary": "Zdroj aplikací který respektuje vaši svobodu a soukromí" + }, + "cy": { + "description": "Mae F-Droid yn gatalog y gellir ei osod o apiau meddalwedd rhyddar gyfer Android.", + "name": "F-Droid", + "summary": "Yr ystorfa apiau sy'n parchu rhyddid a phreifatrwydd" + }, + "de": { + "description": "F-Droid ist ein installierbarer Katalog mit Libre SoftwareAndroid-Apps.", + "summary": "Der App-Store, der Freiheit und Privatsphäre respektiert" + }, + "el": { + "description": "Το F-Droid είναι ένας κατάλογος εφαρμογών ελεύθερου λογισμικού", + "name": "F-Droid", + "summary": "Το κατάστημα εφαρμογών που σέβεται την ελευθερία και την ιδιωτικότητα" + }, + "en-US": { + "description": "F-Droid is an installable catalogue of libre software", + "name": "F-Droid", + "whatsNew": "* Overhaul Share menu to use built-in options like Nearby", + "phoneScreenshots": [ + "screenshot-app-details.png", + "screenshot-dark-details.png", + "screenshot-dark-home.png", + "screenshot-dark-knownvuln.png", + "screenshot-knownvuln.png", + "screenshot-search.png", + "screenshot-updates.png" + ], + "featureGraphic": "featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + "summary": "The app store that respects freedom and privacy" + }, + "eo": { + "description": "F-Droid estas instalebla katalogo de liberaj aplikaĵoj por Android.", + "name": "F-Droid", + "whatsNew": "• rekonstruita menuo “Kunhavigi” por uzi enkonstruitajn eblojn, ekz.", + "summary": "Aplikaĵa vendejo respektanta liberecon kaj privatecon" + } + } + } + ], + "packages": { + "org.fdroid.min1": [ + { + "added": 2342, + "apkName": "org.fdroid.min1_23_2.apk", + "hash": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "hashType": "sha256", + "packageName": "org.fdroid.min1", + "size": 1338, + "srcname": "org.fdroid.min1_23_2.zip", + "uses-permission": [ + [ + "perm", + null + ] + ], + "versionCode": 1, + "versionName": "1", + "nativecode": [ + "x86" + ], + "features": [ + "feature" + ], + "antiFeatures": [ + "anti-feature" + ] + }, + { + "apkName": "org.fdroid.min1_42.apk", + "hash": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "hashType": "sha256", + "minSdkVersion": 21, + "maxSdkVersion": 4568, + "targetSdkVersion": 32, + "packageName": "org.fdroid.min1", + "sig": "old", + "signer": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337, + "srcname": "org.fdroid.min1_42.zip", + "versionCode": 24, + "versionName": "24" + } + ], + "org.fdroid.fdroid": [ + { + "added": 1643250075000, + "apkName": "org.fdroid.fdroid_1014050.apk", + "hash": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8165518, + "srcname": "org.fdroid.fdroid_1014050_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014050, + "versionName": "1.14" + }, + { + "added": 1642785071000, + "apkName": "org.fdroid.fdroid_1014005.apk", + "hash": "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8382606, + "srcname": "org.fdroid.fdroid_1014005_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014005, + "versionName": "1.14-alpha5", + "nativecode": [ + "fakeNativeCode" + ], + "features": [ + "fake feature" + ], + "antiFeatures": [ + "FakeAntiFeature" + ] + }, + { + "added": 1635169849000, + "apkName": "org.fdroid.fdroid_1014003.apk", + "hash": "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8276110, + "srcname": "org.fdroid.fdroid_1014003_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014003, + "versionName": "1.14-alpha3" + }, + { + "added": 1632281731000, + "apkName": "org.fdroid.fdroid_1014002.apk", + "hash": "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8284386, + "srcname": "org.fdroid.fdroid_1014002_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014002, + "versionName": "1.14-alpha2" + }, + { + "added": 1632281729000, + "apkName": "org.fdroid.fdroid_1014001.apk", + "hash": "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610", + "hashType": "sha256", + "minSdkVersion": 22, + "targetSdkVersion": 25, + "packageName": "org.fdroid.fdroid", + "sig": "9063aaadfff9cfd811a9c72fb5012f28", + "signer": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "size": 8272166, + "srcname": "org.fdroid.fdroid_1014001_src.tar.gz", + "uses-permission": [ + [ + "android.permission.INTERNET", + null + ], + [ + "android.permission.ACCESS_NETWORK_STATE", + null + ], + [ + "android.permission.ACCESS_WIFI_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + null + ], + [ + "android.permission.CHANGE_NETWORK_STATE", + null + ], + [ + "android.permission.CHANGE_WIFI_STATE", + null + ], + [ + "android.permission.BLUETOOTH", + null + ], + [ + "android.permission.BLUETOOTH_ADMIN", + null + ], + [ + "android.permission.RECEIVE_BOOT_COMPLETED", + null + ], + [ + "android.permission.READ_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_EXTERNAL_STORAGE", + null + ], + [ + "android.permission.WRITE_SETTINGS", + null + ], + [ + "android.permission.NFC", + null + ], + [ + "android.permission.USB_PERMISSION", + 22 + ], + [ + "android.permission.WAKE_LOCK", + null + ], + [ + "android.permission.FOREGROUND_SERVICE", + null + ] + ], + "uses-permission-sdk-23": [ + [ + "android.permission.ACCESS_COARSE_LOCATION", + null + ] + ], + "versionCode": 1014001, + "versionName": "1.14-alpha1" + } + ] + } +} diff --git a/index/src/sharedTest/resources/index-mid-v2.json b/index/src/sharedTest/resources/index-mid-v2.json new file mode 100644 index 000000000..a6cb2b8cb --- /dev/null +++ b/index/src/sharedTest/resources/index-mid-v2.json @@ -0,0 +1,660 @@ +{ + "repo": { + "name": "MidV1", + "icon": { + "name": "/icons/mid-v1.png", + "sha256": "74758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 234232352235 + }, + "address": "https://mid-v1.org", + "description": { + "en-US": "This is a repo with medium data.", + "de": "Dies ist ein Repo mit mittlerer Datendichte." + }, + "mirrors": [ + { + "url": "https://mid-v1.com" + } + ], + "timestamp": 1337, + "antiFeatures": { + "AntiFeature": { + "name": { + "en-US": "AntiFeature" + }, + "description": { + "en-US": "A bad anti-feature, we can't show to users." + } + } + }, + "categories": { + "Cat1": { + "icon": { + "name": "/icons/cat2.png", + "sha256": "54758e480ae76297c7947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 9223372036854775807 + }, + "name": { + "en-US": "Cat1" + } + }, + "System": { + "name": { + } + } + } + }, + "packages": { + "org.fdroid.min1": { + "metadata": { + "name": { + "en-US": "App1", + "de" : "app 1 name" + }, + "summary": { + "en-US": "App1 summary", + "de": "App1 Zusammenfassung" + }, + "description": { + "en-US": "App1 description", + "de": "App1 beschreibung" + }, + "added": 1234567890, + "lastUpdated": 1234567891, + "webSite": "http://min1.test.org", + "license": "GPLv3", + "categories": [ + "Cat1" + ], + "author": { + "name": "App1 author" + }, + "icon": { + "en-US": { + "name": "/icons/icon-min1.png", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337 + } + } + }, + "versions": { + "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": { + "added": 0, + "file": { + "name": "/org.fdroid.min1_42.apk", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337 + }, + "src": { + "name": "/org.fdroid.min1_42.zip", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1338 + }, + "manifest": { + "versionName": "24", + "versionCode": 24, + "usesSdk": { + "minSdkVersion": 21, + "targetSdkVersion": 32 + }, + "maxSdkVersion": 4568, + "signer": { + "sha256": [ + "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf" + ] + } + }, + "releaseChannels": [ + "Beta" + ], + "antiFeatures": { + "AntiFeature": { + "en-US": "reason" + } + } + } + } + }, + "org.fdroid.fdroid": { + "metadata": { + "name": { + "af": "-درويد", + "be": "F-Droid", + "bn": "এফ-ড্রয়েড", + "ca": "F-Droid", + "cs": "F-Droid", + "cy": "F-Droid", + "el": "F-Droid", + "en-US": "F-Droid", + "eo": "F-Droid" + }, + "summary": { + "af": "متجر التطبيقات الذي يحترم الحرية والخصوصية)", + "be": "Крама праграм, якая паважае свабоду і прыватнасць", + "bg": "Магазинът за приложения, който уважава независимостта и поверителността", + "bn": "যে অ্যাপ স্টোর স্বাধীনতা ও গোপনীয়তা সম্মান করে", + "bo": "རང་དབང་དང་གསང་དོན་ལ་གུས་བརྩི་ཞུས་མཁན་གྱི་མཉེན་ཆས་ཉར་ཚགས་ཁང་།", + "ca": "La botiga d'aplicacions que respecta la llibertat i la privacitat", + "cs": "Zdroj aplikací který respektuje vaši svobodu a soukromí", + "cy": "Yr ystorfa apiau sy'n parchu rhyddid a phreifatrwydd", + "de": "Der App-Store, der Freiheit und Privatsphäre respektiert", + "el": "Το κατάστημα εφαρμογών που σέβεται την ελευθερία και την ιδιωτικότητα", + "en-US": "The app store that respects freedom and privacy", + "eo": "Aplikaĵa vendejo respektanta liberecon kaj privatecon" + }, + "description": { + "af": "F-Droid is 'n installeerbare katalogus van gratis sagteware", + "bo": "ཨེཕ་རོཌ་ནི་ཨེན་ཀྲོཌ་བབ་སྟེགས་ཀྱི་ཆེད་དུ་FOSS", + "ca": "F-Droid és un catàleg instal·lable d'aplicacions de software lliure", + "cs": "F-Droid je instalovatelný katalog softwarových libre", + "cy": "Mae F-Droid yn gatalog y gellir ei osod o apiau meddalwedd rhyddar gyfer Android.", + "de": "F-Droid ist ein installierbarer Katalog mit Libre SoftwareAndroid-Apps.", + "el": "Το F-Droid είναι ένας κατάλογος εφαρμογών ελεύθερου λογισμικού", + "en-US": "F-Droid is an installable catalogue of libre software", + "eo": "F-Droid estas instalebla katalogo de liberaj aplikaĵoj por Android." + }, + "added": 1295222400000, + "lastUpdated": 1643250075000, + "webSite": "https://f-droid.org", + "changelog": "https://gitlab.com/fdroid/fdroidclient/raw/HEAD/CHANGELOG.md", + "license": "GPL-3.0-or-later", + "sourceCode": "https://gitlab.com/fdroid/fdroidclient", + "issueTracker": "https://gitlab.com/fdroid/fdroidclient/issues", + "translation": "https://hosted.weblate.org/projects/f-droid/f-droid", + "preferredSigner": "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab", + "categories": [ + "System" + ], + "donation": { + "url": "https://f-droid.org/donate", + "liberapayID": "27859", + "openCollective": "F-Droid-Euro", + "flattrID": "343053" + }, + "icon": { + "en-US": { + "name": "/icons/org.fdroid.fdroid.1014050.png", + "sha256": "224a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1237 + } + }, + "featureGraphic": { + "en-US": { + "name": "/org.fdroid.fdroid/en-US/featureGraphic_PTun9TO4cMFOeiqbvQSrkdcxNUcOFQCymMIaj9UJOAY=.jpg", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 4237 + } + }, + "screenshots": { + "phone": { + "en-US": [ + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-app-details.png", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 4237 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-dark-details.png", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 44287 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-dark-home.png", + "sha256": "424a109b2352138c3699760e1673385d0ed50ce526fc7982f8d65757743374bf", + "size": 4587 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-dark-knownvuln.png", + "sha256": "424a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 445837 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-knownvuln.png", + "sha256": "424a109b2352138c4599760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 4287 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-search.png", + "sha256": "424a109b2352138c3694760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 2857 + }, + { + "name": "/org.fdroid.fdroid/en-US/phoneScreenshots/screenshot-updates.png", + "sha256": "424a109b2352138c3699750e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 485287 + } + ] + } + } + }, + "versions": { + "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06": { + "added": 1643250075000, + "file": { + "name": "/org.fdroid.fdroid_1014050.apk", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06", + "size": 8165518 + }, + "src": { + "name": "/org.fdroid.fdroid_1014050_src.tar.gz", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d07", + "size": 8165519 + }, + "manifest": { + "versionName": "1.14", + "versionCode": 1014050, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ] + }, + "whatsNew": { + "en-US": "* Overhaul Share menu to use built-in options like Nearby", + "eo": "• rekonstruita menuo “Kunhavigi” por uzi enkonstruitajn eblojn, ekz." + } + }, + "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d": { + "added": 1642785071000, + "file": { + "name": "/org.fdroid.fdroid_1014005.apk", + "sha256": "b4282febf5558d43c7c51a00478961f6df1b6d59e0a6674974cdacb792683e5d", + "size": 8382606 + }, + "src": { + "name": "/org.fdroid.fdroid_1014005_src.tar.gz", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d07", + "size": 8165519 + }, + "manifest": { + "versionName": "1.14-alpha5", + "versionCode": 1014005, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ], + "nativecode": [ + "fakeNativeCode" + ], + "features": [ + { + "name": "fake feature" + } + ] + }, + "antiFeatures": { + "FakeAntiFeature": { + } + } + }, + "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3": { + "added": 1635169849000, + "file": { + "name": "/org.fdroid.fdroid_1014003.apk", + "sha256": "c062a9642fde08aacabbfa4cab1ab5773c83f4e6b81551ffd92027d2b20f37d3", + "size": 8276110 + }, + "src": { + "name": "/org.fdroid.fdroid_1014003_src.tar.gz", + "sha256": "8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + "size": 8165519 + }, + "manifest": { + "versionName": "1.14-alpha3", + "versionCode": 1014003, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ] + } + }, + "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1": { + "added": 1632281731000, + "file": { + "name": "/org.fdroid.fdroid_1014002.apk", + "sha256": "3243c24ee95be0fce0830d72e7d2605e3e24f6ccf4ee72a7c8e720fccd7621a1", + "size": 8284386 + }, + "src": { + "name": "/org.fdroid.fdroid_1014002_src.tar.gz", + "sha256": "7c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + "size": 7165519 + }, + "manifest": { + "versionName": "1.14-alpha2", + "versionCode": 1014002, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ] + } + }, + "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610": { + "added": 1632281729000, + "file": { + "name": "/org.fdroid.fdroid_1014001.apk", + "sha256": "7ebfd5eb76f9ec95ba955e549260fe930dc38fb99ed3532f92c93b879aca5610", + "size": 8272166 + }, + "src": { + "name": "/org.fdroid.fdroid_1014001_src.tar.gz", + "sha256": "6c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cb067d510ac6f3e0d07", + "size": 6165519 + }, + "manifest": { + "versionName": "1.14-alpha1", + "versionCode": 1014001, + "usesSdk": { + "minSdkVersion": 22, + "targetSdkVersion": 25 + }, + "signer": { + "sha256": [ + "43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab" + ] + }, + "usesPermission": [ + { + "name": "android.permission.INTERNET" + }, + { + "name": "android.permission.ACCESS_NETWORK_STATE" + }, + { + "name": "android.permission.ACCESS_WIFI_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_MULTICAST_STATE" + }, + { + "name": "android.permission.CHANGE_NETWORK_STATE" + }, + { + "name": "android.permission.CHANGE_WIFI_STATE" + }, + { + "name": "android.permission.BLUETOOTH" + }, + { + "name": "android.permission.BLUETOOTH_ADMIN" + }, + { + "name": "android.permission.RECEIVE_BOOT_COMPLETED" + }, + { + "name": "android.permission.READ_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_EXTERNAL_STORAGE" + }, + { + "name": "android.permission.WRITE_SETTINGS" + }, + { + "name": "android.permission.NFC" + }, + { + "name": "android.permission.USB_PERMISSION", + "maxSdkVersion": 22 + }, + { + "name": "android.permission.WAKE_LOCK" + }, + { + "name": "android.permission.FOREGROUND_SERVICE" + } + ], + "usesPermissionSdk23": [ + { + "name": "android.permission.ACCESS_COARSE_LOCATION" + } + ] + } + } + } + } + } +} diff --git a/index/src/sharedTest/resources/index-min-reordered-v2.json b/index/src/sharedTest/resources/index-min-reordered-v2.json new file mode 100644 index 000000000..217f534ab --- /dev/null +++ b/index/src/sharedTest/resources/index-min-reordered-v2.json @@ -0,0 +1,38 @@ +{ + "packages": { + "org.fdroid.min1": { + "versions": { + "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": { + "added": 0, + "manifest": { + "versionName": "0", + "versionCode": 1 + }, + "file": { + "name": "/org.fdroid.min1_23.apk", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337 + } + } + }, + "metadata": { + "added": 0, + "lastUpdated": 0, + "license": "" + } + } + }, + "repo": { + "name": "MinV1", + "icon": { + "name": "/icons/min-v1.png", + "sha256": "74758e480ae76297c8947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + "address": "https://min-v1.org", + "description": { + "en-US": "This is a repo with minimal data." + }, + "timestamp": 42 + } +} diff --git a/index/src/sharedTest/resources/index-min-v1.json b/index/src/sharedTest/resources/index-min-v1.json new file mode 100644 index 000000000..63d8aacb4 --- /dev/null +++ b/index/src/sharedTest/resources/index-min-v1.json @@ -0,0 +1,34 @@ +{ + "repo": { + "timestamp": 42, + "version": 1, + "name": "MinV1", + "icon": "min-v1.png", + "address": "https://min-v1.org", + "description": "This is a repo with minimal data." + }, + "requests": { + "install": [ + ], + "uninstall": [ + ] + }, + "apps": [ + { + "license": "", + "packageName": "org.fdroid.min1" + } + ], + "packages": { + "org.fdroid.min1": [ + { + "apkName": "org.fdroid.min1_23.apk", + "hash": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "hashType": "sha256", + "packageName": "org.fdroid.min1", + "size": 1337, + "versionName": "0" + } + ] + } +} diff --git a/index/src/sharedTest/resources/index-min-v2.json b/index/src/sharedTest/resources/index-min-v2.json new file mode 100644 index 000000000..6bf3a6160 --- /dev/null +++ b/index/src/sharedTest/resources/index-min-v2.json @@ -0,0 +1,38 @@ +{ + "repo": { + "name": "MinV1", + "icon": { + "name": "/icons/min-v1.png", + "sha256": "74758e480ae76297c8947f107db9ea03d2933c9d5c110d02046977cf78d43def", + "size": 0 + }, + "address": "https://min-v1.org", + "description": { + "en-US": "This is a repo with minimal data." + }, + "timestamp": 42 + }, + "packages": { + "org.fdroid.min1": { + "metadata": { + "added": 0, + "lastUpdated": 0, + "license": "" + }, + "versions": { + "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": { + "added": 0, + "file": { + "name": "/org.fdroid.min1_23.apk", + "sha256": "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf", + "size": 1337 + }, + "manifest": { + "versionName": "0", + "versionCode": 1 + } + } + } + } + } +}