Skip to content

Commit

Permalink
Merge pull request #825 from machiav3lli/main
Browse files Browse the repository at this point in the history
  • Loading branch information
Iamlooker authored Sep 4, 2024
2 parents 1d40bc5 + b732774 commit 0833ee2
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ object Cache {
private const val RELEASE_DIR = "releases"
private const val PARTIAL_DIR = "partial"
private const val IMAGES_DIR = "images"
private const val INDEX_DIR = "index"
private const val TEMP_DIR = "temporary"

private fun ensureCacheDir(context: Context, name: String): File {
Expand Down Expand Up @@ -52,6 +53,10 @@ object Cache {
return ensureCacheDir(context, IMAGES_DIR)
}

fun getIndexFile(context: Context, indexName: String): File {
return File(ensureCacheDir(context, INDEX_DIR), indexName)
}

fun getPartialReleaseFile(context: Context, cacheFileName: String): File {
return File(ensureCacheDir(context, PARTIAL_DIR), cacheFileName)
}
Expand Down Expand Up @@ -108,9 +113,10 @@ object Cache {
cleanup(
context,
Pair(IMAGES_DIR, Duration.INFINITE),
Pair(INDEX_DIR, Duration.INFINITE),
Pair(PARTIAL_DIR, 24.hours),
Pair(RELEASE_DIR, 24.hours),
Pair(TEMP_DIR, 1.hours)
Pair(TEMP_DIR, 1.hours),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.looker.core.data.repository

import android.content.Context
import com.looker.core.common.extension.exceptCancellation
import com.looker.core.data.fdroid.toEntity
import com.looker.core.database.dao.AppDao
Expand All @@ -13,6 +14,7 @@ import com.looker.core.domain.RepoRepository
import com.looker.core.domain.model.Repo
import com.looker.network.Downloader
import com.looker.sync.fdroid.v2.EntrySyncable
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
Expand All @@ -26,6 +28,7 @@ import kotlinx.coroutines.withContext
import javax.inject.Inject

class OfflineFirstRepoRepository @Inject constructor(
@ApplicationContext context: Context,
private val appDao: AppDao,
private val repoDao: RepoDao,
private val settingsRepository: SettingsRepository,
Expand Down Expand Up @@ -62,7 +65,7 @@ class OfflineFirstRepoRepository @Inject constructor(
}
}

private val syncable = EntrySyncable(downloader, dispatcher)
private val syncable = EntrySyncable(context, downloader, dispatcher)

override suspend fun sync(repo: Repo): Boolean = coroutineScope {
try {
Expand Down
8 changes: 4 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Taken from NIA sample app by Google
[versions]
androidDesugarJdkLibs = "2.0.4"
androidGradlePlugin = "8.5.2"
androidDesugarJdkLibs = "2.1.1"
androidGradlePlugin = "8.6.0"
androidMaterial = "1.12.0"
androidxActivity = "1.9.1"
androidxAppCompat = "1.7.0"
Expand All @@ -26,11 +26,11 @@ hilt = "2.52"
hiltExt = "1.2.0"
junit4 = "4.13.2"
jackson = "2.17.2"
kotlin = "2.0.10"
kotlin = "2.0.20"
kotlinxCoroutines = "1.9.0-RC.2"
kotlinxDatetime = "0.6.0"
kotlinxSerializationJson = "1.7.1"
ksp = "2.0.10-1.0.24"
ksp = "2.0.20-1.0.24"
ktlint = "12.1.1"
ktor = "2.3.12"
libsu = "6.0.0"
Expand Down
11 changes: 8 additions & 3 deletions sync/fdroid/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
plugins {
alias(libs.plugins.looker.jvm.library)
alias(libs.plugins.looker.android.library)
alias(libs.plugins.looker.serialization)
alias(libs.plugins.looker.lint)
}

android {
namespace = "com.looker.sync.fdroid"
}

dependencies {
modules(
Modules.coreCommon,
Modules.coreDomain,
Modules.coreNetwork,
)
Expand All @@ -18,9 +23,9 @@ dependencies {
testRuntimeOnly(libs.junit.platform)
}

tasks.test {
/*tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
}*/
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.looker.sync.fdroid.common

import android.content.Context
import com.looker.core.common.cache.Cache
import com.looker.core.domain.model.Repo
import com.looker.network.Downloader
import kotlinx.coroutines.Dispatchers
Expand All @@ -8,11 +10,13 @@ import java.io.File
import java.util.Date

suspend fun Downloader.downloadIndex(
context: Context,
repo: Repo,
fileName: String,
url: String,
diff: Boolean = false,
): File = withContext(Dispatchers.IO) {
val tempFile = File.createTempFile(repo.name, fileName)
val tempFile = Cache.getIndexFile(context, "repo_${repo.id}_$fileName")
downloadToFile(
url = url,
target = tempFile,
Expand All @@ -23,10 +27,14 @@ suspend fun Downloader.downloadIndex(
repo.authentication.password
)
}
if (repo.versionInfo.timestamp > 0L) {
if (repo.versionInfo.timestamp > 0L && !diff) {
ifModifiedSince(Date(repo.versionInfo.timestamp))
}
}
)
tempFile
}

const val INDEX_V1_NAME = "index-v1.jar"
const val ENTRY_V2_NAME = "entry.jar"
const val INDEX_V2_NAME = "index-v2.json"
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.looker.sync.fdroid.v1

import android.content.Context
import com.looker.core.domain.model.Fingerprint
import com.looker.core.domain.model.Repo
import com.looker.network.Downloader
import com.looker.sync.fdroid.Parser
import com.looker.sync.fdroid.Syncable
import com.looker.sync.fdroid.common.INDEX_V1_NAME
import com.looker.sync.fdroid.common.IndexJarValidator
import com.looker.sync.fdroid.common.JsonParser
import com.looker.sync.fdroid.common.downloadIndex
Expand All @@ -15,6 +17,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext

class V1Syncable(
private val context: Context,
private val downloader: Downloader,
private val dispatcher: CoroutineDispatcher,
) : Syncable<IndexV1> {
Expand All @@ -28,9 +31,10 @@ class V1Syncable(
override suspend fun sync(repo: Repo): Pair<Fingerprint, IndexV2> =
withContext(dispatcher) {
val jar = downloader.downloadIndex(
context = context,
repo = repo,
url = repo.address.removeSuffix("/") + "/index-v1.jar",
fileName = "index-v1.jar",
url = repo.address.removeSuffix("/") + "/$INDEX_V1_NAME",
fileName = INDEX_V1_NAME,
)
val (fingerprint, indexV1) = parser.parse(jar, repo)
jar.delete()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@ package com.looker.sync.fdroid.v2
import com.looker.core.domain.model.Fingerprint
import com.looker.core.domain.model.Repo
import com.looker.sync.fdroid.Parser
import com.looker.sync.fdroid.v2.model.IndexV2
import com.looker.sync.fdroid.v2.model.IndexV2Diff
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import java.io.File

class DiffParser: Parser<IndexV2> {
override suspend fun parse(file: File, repo: Repo): Pair<Fingerprint, IndexV2> {
TODO("Not yet implemented")
}
class DiffParser(
private val dispatcher: CoroutineDispatcher,
private val json: Json,
) : Parser<IndexV2Diff> {
@OptIn(ExperimentalSerializationApi::class)
override suspend fun parse(file: File, repo: Repo): Pair<Fingerprint, IndexV2Diff> =
withContext(dispatcher) {
val indexV2 = file.inputStream().use {
json.decodeFromStream(IndexV2Diff.serializer(), it)
}
requireNotNull(repo.fingerprint) {
"Fingerprint should not be null when parsing diff"
} to indexV2
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package com.looker.sync.fdroid.v2

import android.content.Context
import com.looker.core.common.cache.Cache
import com.looker.core.domain.model.Fingerprint
import com.looker.core.domain.model.Repo
import com.looker.network.Downloader
import com.looker.sync.fdroid.Parser
import com.looker.sync.fdroid.Syncable
import com.looker.sync.fdroid.common.ENTRY_V2_NAME
import com.looker.sync.fdroid.common.INDEX_V2_NAME
import com.looker.sync.fdroid.common.IndexJarValidator
import com.looker.sync.fdroid.common.JsonParser
import com.looker.sync.fdroid.common.downloadIndex
import com.looker.sync.fdroid.v2.model.Entry
import com.looker.sync.fdroid.v2.model.IndexV2
import com.looker.sync.fdroid.v2.model.IndexV2Diff
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream

class EntrySyncable(
private val context: Context,
private val downloader: Downloader,
private val dispatcher: CoroutineDispatcher,
) : Syncable<Entry> {
Expand All @@ -30,23 +39,52 @@ class EntrySyncable(
json = JsonParser.parser,
)

private val diffParser: Parser<IndexV2Diff> = DiffParser(
dispatcher = dispatcher,
json = JsonParser.parser,
)

@OptIn(ExperimentalSerializationApi::class)
override suspend fun sync(repo: Repo): Pair<Fingerprint, IndexV2?> =
withContext(Dispatchers.IO) {
// example https://apt.izzysoft.de/fdroid/repo/entry.json
val jar = downloader.downloadIndex(
context = context,
repo = repo,
url = repo.address.removeSuffix("/") + "/entry.jar",
fileName = "entry.jar"
url = repo.address.removeSuffix("/") + "/$ENTRY_V2_NAME",
fileName = ENTRY_V2_NAME
)
val (fingerprint, entry) = parser.parse(jar, repo)
jar.delete()
val index = entry.getDiff(repo.versionInfo.timestamp)
?: return@withContext fingerprint to null
val indexPath = repo.address.removeSuffix("/") + index.name
val indexFile = downloader.downloadIndex(
repo = repo,
url = indexPath,
fileName = "index-v2.json"
)
val (_, indexV2) = indexParser.parse(indexFile, repo)
val indexFile = Cache.getIndexFile(context, "repo_${repo.id}_$INDEX_V2_NAME")
val indexV2 = if (index != entry.index && indexFile.exists()) {
// example https://apt.izzysoft.de/fdroid/repo/diff/1725372028000.json
val diffFile = downloader.downloadIndex(
context = context,
repo = repo,
url = indexPath,
fileName = "diff_${repo.versionInfo.timestamp}.json",
diff = true,
)
diffParser.parse(diffFile, repo).second.let {
diffFile.delete()
it.patchInto(indexParser.parse(indexFile, repo).second) { index ->
Json.encodeToStream(index, indexFile.outputStream())
}
}
} else {
// example https://apt.izzysoft.de/fdroid/repo/index-v2.json
val newIndexFile = downloader.downloadIndex(
context = context,
repo = repo,
url = indexPath,
fileName = INDEX_V2_NAME,
)
indexParser.parse(newIndexFile, repo).second
}
fingerprint to indexV2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,28 @@ data class IndexV2(
val repo: RepoV2,
val packages: Map<String, PackageV2>
)

@Serializable
data class IndexV2Diff(
val repo: RepoV2Diff,
val packages: Map<String, PackageV2Diff?>
) {
fun patchInto(index: IndexV2, saveIndex: (IndexV2) -> Unit): IndexV2 {
val packagesToRemove = packages.filter { it.value == null }.keys
val packagesToAdd = packages
.mapNotNull { (key, value) ->
value?.let { value ->
if (index.packages.keys.contains(key))
index.packages[key]?.let { value.patchInto(it) }
else value.toPackage()
}?.let { key to it }
}

val newIndex = index.copy(
repo = repo.patchInto(index.repo),
packages = index.packages.minus(packagesToRemove).plus(packagesToAdd),
)
saveIndex(newIndex)
return newIndex
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.looker.sync.fdroid.v2.model

typealias LocalizedString = Map<String, String>
typealias NullableLocalizedString = Map<String, String?>
typealias LocalizedIcon = Map<String, FileV2>
typealias LocalizedList = Map<String, List<String>>
typealias LocalizedFiles = Map<String, List<FileV2>>
Loading

0 comments on commit 0833ee2

Please sign in to comment.