Skip to content

Commit

Permalink
Add data classes for index v2 (Touches #287)
Browse files Browse the repository at this point in the history
Signed-off-by: LooKeR <mohit2002ss@gmail.com>
  • Loading branch information
Iamlooker committed Mar 19, 2023
1 parent f896f50 commit d6c54ed
Show file tree
Hide file tree
Showing 12 changed files with 437 additions and 0 deletions.
43 changes: 43 additions & 0 deletions core/data/src/main/java/com/looker/core/data/fdroid/IndexParser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.looker.core.data.fdroid

import com.looker.core.data.fdroid.model.v1.IndexV1
import com.looker.core.data.fdroid.model.v2.Entry
import com.looker.core.data.fdroid.model.v2.IndexV2
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

object IndexParser {

@Volatile
private var JSON: Json? = null

/**
* Initializing [Json] is expensive, so using this method is preferable as it keeps returning
* a single instance with the recommended settings.
*/
val json: Json
@JvmStatic
get() {
return JSON ?: synchronized(this) {
Json {
ignoreUnknownKeys = true
}
}
}

@JvmStatic
fun parseV1(str: String): IndexV1 {
return json.decodeFromString(str)
}

@JvmStatic
fun parseV2(str: String): IndexV2 {
return json.decodeFromString(str)
}

@JvmStatic
fun parseEntry(str: String): Entry {
return json.decodeFromString(str)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.looker.core.data.fdroid.model

interface IndexFile {
val name: String
val sha256: String?
val size: Long?
val ipfsCidV1: String?

suspend fun serialize(): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.looker.core.data.fdroid.model.v2

import com.looker.core.data.fdroid.IndexParser.json
import com.looker.core.data.fdroid.model.IndexFile
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

@Serializable
data class Entry(
val timestamp: Long,
val version: Long,
val maxAge: Int? = null,
val index: EntryFileV2,
val diffs: Map<String, EntryFileV2> = emptyMap(),
) {
/**
* @return the diff for the given [timestamp] or null if none exists
* in which case the full [index] should be used.
*/
fun getDiff(timestamp: Long): EntryFileV2? {
return diffs[timestamp.toString()]
}
}


@Serializable
data class EntryFileV2(
override val name: String,
override val sha256: String,
override val size: Long,
@SerialName("ipfsCIDv1")
override val ipfsCidV1: String? = null,
val numPackages: Int,
) : IndexFile {
companion object {
fun deserialize(string: String): EntryFileV2 {
return json.decodeFromString(string)
}
}

override suspend fun serialize(): String {
return json.encodeToString(this)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.looker.core.data.fdroid.model.v2

import com.looker.core.data.fdroid.IndexParser.json
import com.looker.core.data.fdroid.model.IndexFile
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString


@Serializable
data class FileV2(
override val name: String,
override val sha256: String? = null,
override val size: Long? = null,
@SerialName("ipfsCIDv1")
override val ipfsCidV1: String? = null,
) : IndexFile {
companion object {
@JvmStatic
fun deserialize(string: String?): FileV2? {
if (string == null) return null
return json.decodeFromString(string)
}

@JvmStatic
fun fromPath(path: String): FileV2 = FileV2(path)
}

override suspend fun serialize(): String {
return json.encodeToString(this)
}
}

typealias LocalizedTextV2 = Map<String, String>
typealias LocalizedFileV2 = Map<String, FileV2>
typealias LocalizedFileListV2 = Map<String, List<FileV2>>

@Serializable
data class MirrorV2(
val url: String,
val location: String? = null,
)

@Serializable
data class AntiFeatureV2(
val icon: LocalizedFileV2 = emptyMap(),
val name: LocalizedTextV2,
val description: LocalizedTextV2 = emptyMap(),
)

@Serializable
data class CategoryV2(
val icon: LocalizedFileV2 = emptyMap(),
val name: LocalizedTextV2,
val description: LocalizedTextV2 = emptyMap(),
)

@Serializable
data class ReleaseChannelV2(
val name: LocalizedTextV2,
val description: LocalizedTextV2 = emptyMap(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.looker.core.data.fdroid.model.v2

import kotlinx.serialization.Serializable

@Serializable
public data class IndexV2(
val repo: RepoV2,
val packages: Map<String, PackageV2> = emptyMap(),
) {
public fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
repo.walkFiles(fileConsumer)
packages.values.forEach { it.walkFiles(fileConsumer) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.looker.core.data.fdroid.model.v2

import com.looker.core.data.fdroid.IndexParser
import com.looker.core.data.fdroid.model.IndexFile
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString

@Serializable
data class PackageV2(
val metadata: MetadataV2,
val versions: Map<String, PackageVersionV2> = emptyMap(),
) {
fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
metadata.walkFiles(fileConsumer)
versions.values.forEach { it.walkFiles(fileConsumer) }
}
}

@Serializable
data class MetadataV2(
val name: LocalizedTextV2? = null,
val summary: LocalizedTextV2? = null,
val description: LocalizedTextV2? = null,
val added: Long,
val lastUpdated: Long,
val webSite: String? = null,
val changelog: String? = null,
val license: String? = null,
val sourceCode: String? = null,
val issueTracker: String? = null,
val translation: String? = null,
val preferredSigner: String? = null,
val categories: List<String> = emptyList(),
val authorName: String? = null,
val authorEmail: String? = null,
val authorWebSite: String? = null,
val authorPhone: String? = null,
val donate: List<String> = emptyList(),
val liberapayID: String? = null,
val liberapay: String? = null,
val openCollective: String? = null,
val bitcoin: String? = null,
val litecoin: String? = null,
val flattrID: String? = null,
val icon: LocalizedFileV2? = null,
val featureGraphic: LocalizedFileV2? = null,
val promoGraphic: LocalizedFileV2? = null,
val tvBanner: LocalizedFileV2? = null,
val video: LocalizedTextV2? = null,
val screenshots: Screenshots? = null,
) {
fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
icon?.values?.forEach { fileConsumer(it) }
featureGraphic?.values?.forEach { fileConsumer(it) }
promoGraphic?.values?.forEach { fileConsumer(it) }
tvBanner?.values?.forEach { fileConsumer(it) }
screenshots?.phone?.values?.forEach { it.forEach(fileConsumer) }
screenshots?.sevenInch?.values?.forEach { it.forEach(fileConsumer) }
screenshots?.tenInch?.values?.forEach { it.forEach(fileConsumer) }
screenshots?.wear?.values?.forEach { it.forEach(fileConsumer) }
screenshots?.tv?.values?.forEach { it.forEach(fileConsumer) }
}
}


@Serializable
data class Screenshots(
val phone: LocalizedFileListV2? = null,
val sevenInch: LocalizedFileListV2? = null,
val tenInch: LocalizedFileListV2? = null,
val wear: LocalizedFileListV2? = null,
val tv: LocalizedFileListV2? = null,
) {
val isNull: Boolean
get() = phone == null && sevenInch == null && tenInch == null && wear == null && tv == null
}

interface PackageVersion {
val versionCode: Long
val signer: SignerV2?
val releaseChannels: List<String>?
val packageManifest: PackageManifest
val hasKnownVulnerability: Boolean
}

const val ANTI_FEATURE_KNOWN_VULNERABILITY: String = "KnownVuln"

@Serializable
data class PackageVersionV2(
val added: Long,
val file: FileV1,
val src: FileV2? = null,
val manifest: ManifestV2,
override val releaseChannels: List<String> = emptyList(),
val antiFeatures: Map<String, LocalizedTextV2> = emptyMap(),
val whatsNew: LocalizedTextV2 = emptyMap(),
) : PackageVersion {
override val versionCode: Long = manifest.versionCode
override val signer: SignerV2? = manifest.signer
override val packageManifest: PackageManifest = manifest
override val hasKnownVulnerability: Boolean
get() = antiFeatures.contains(ANTI_FEATURE_KNOWN_VULNERABILITY)

fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
fileConsumer(src)
}
}

@Serializable
data class FileV1(
override val name: String,
override val sha256: String,
override val size: Long? = null,
@SerialName("ipfsCIDv1")
override val ipfsCidV1: String? = null,
) : IndexFile {
companion object {
@JvmStatic
fun deserialize(string: String?): FileV1? {
if (string == null) return null
return IndexParser.json.decodeFromString(string)
}
}

override suspend fun serialize(): String {
return IndexParser.json.encodeToString(this)
}
}


interface PackageManifest {
val minSdkVersion: Int?
val maxSdkVersion: Int?
val featureNames: List<String>?
val nativecode: List<String>?
}

@Serializable
data class ManifestV2(
val versionName: String,
val versionCode: Long,
val usesSdk: UsesSdkV2? = null,
override val maxSdkVersion: Int? = null,
val signer: SignerV2? = null, // yes this can be null for stuff like non-apps
val usesPermission: List<PermissionV2> = emptyList(),
val usesPermissionSdk23: List<PermissionV2> = emptyList(),
override val nativecode: List<String> = emptyList(),
val features: List<FeatureV2> = emptyList(),
) : PackageManifest {
override val minSdkVersion: Int? = usesSdk?.minSdkVersion
override val featureNames: List<String> = features.map { it.name }
}

@Serializable
data class UsesSdkV2(
val minSdkVersion: Int,
val targetSdkVersion: Int,
)

@Serializable
data class SignerV2(
val sha256: List<String>,
val hasMultipleSigners: Boolean = false,
)

@Serializable
data class PermissionV2(
val name: String,
val maxSdkVersion: Int? = null,
)

@Serializable
data class FeatureV2(
val name: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.looker.core.data.fdroid.model.v2

import kotlinx.serialization.Serializable

@Serializable
public data class RepoV2(
val name: LocalizedTextV2 = emptyMap(),
val icon: LocalizedFileV2 = emptyMap(),
val address: String,
val webBaseUrl: String? = null,
val description: LocalizedTextV2 = emptyMap(),
val mirrors: List<MirrorV2> = emptyList(),
val timestamp: Long,
val antiFeatures: Map<String, AntiFeatureV2> = emptyMap(),
val categories: Map<String, CategoryV2> = emptyMap(),
val releaseChannels: Map<String, ReleaseChannelV2> = emptyMap(),
) {
public fun walkFiles(fileConsumer: (FileV2?) -> Unit) {
icon.values.forEach { fileConsumer(it) }
antiFeatures.values.forEach { it.icon.values.forEach { icon -> fileConsumer(icon) } }
categories.values.forEach { it.icon.values.forEach { icon -> fileConsumer(icon) } }
}
}
Loading

2 comments on commit d6c54ed

@grote
Copy link

@grote grote commented on d6c54ed May 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are of course welcome to copy over code from F-Droid, but please include at least authorship and license information.

@Iamlooker
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are of course welcome to copy over code from F-Droid, but please include at least authorship and license information.

Sorry I overlooked it, but I already removed all of these classes. I am writing a simple implementation myself, but I will try the index library :)

Please sign in to comment.