Merge branch 'compileSdk35' into 'master'

Compile with SDK 35 (Android 15)

Closes #2607

See merge request fdroid/fdroidclient!1452
This commit is contained in:
Torsten Grote
2024-10-17 18:27:32 +00:00
52 changed files with 2405 additions and 411 deletions

View File

@@ -1,2 +1,26 @@
root = true
[*]
insert_final_newline = true
[*.{kt, kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
# Disable wildcard imports entirely
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
max_line_length = 100
ktlint_code_style = android_studio
ktlint_standard = enabled
ktlint_experimental = disabled
ktlint_standard_wrapping = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_import-ordering = disabled
ktlint_standard_multiline-if-else = disabled
ktlint_standard_trailing-comma-on-call-site = disabled
ktlint_standard_trailing-comma-on-declaration-site = disabled
ktlint_standard_no-blank-line-before-rbrace = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_function-naming = disabled # for compose only
ktlint_standard_function-signature = disabled

View File

@@ -32,7 +32,7 @@ workflow:
- test -e $cmdline_tools_latest && export PATH="$cmdline_tools_latest:$PATH"
- export GRADLE_USER_HOME=$PWD/.gradle
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdk\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdk = "\([0-9][0-9]*\)".*,\1,p' gradle/libs.versions.toml`
- echo y | sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
# index-v1.jar tests need SHA1 support still, TODO use apksig to validate JAR sigs
- sed -i 's,SHA1 denyAfter 20[0-9][0-9],SHA1 denyAfter 2026,'
@@ -218,8 +218,9 @@ libs database schema:
- apt-get update
- apt-get -qy --no-install-recommends install openjdk-17-jdk-headless git sdkmanager
- export ANDROID_HOME=/opt/android-sdk
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdk\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
- export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdk = "\([0-9][0-9]*\)".*,\1,p' gradle/libs.versions.toml`
- sdkmanager "platforms;android-$ANDROID_COMPILE_SDK" "build-tools;$ANDROID_COMPILE_SDK.0.0"
- sdkmanager "build-tools;34.0.0" # something (AGP?) still pulls in old build-tools
- ./gradlew :libs:database:kaptDebugKotlin
- git --no-pager diff --exit-code

View File

@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
id 'org.jetbrains.kotlin.plugin.compose'
}
// add -Pstrict.release to the gradle command line to enable
@@ -27,9 +27,9 @@ def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
android {
namespace "org.fdroid.fdroid"
buildToolsVersion "34.0.0"
buildToolsVersion "35.0.0"
compileSdk 34
compileSdk libs.versions.compileSdk.get().toInteger()
defaultConfig {
versionCode 1021050
@@ -112,10 +112,6 @@ android {
aidl true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.9"
}
testOptions {
unitTests {
includeAndroidResources = true
@@ -173,75 +169,73 @@ dependencies {
implementation project(":libs:download")
implementation project(":libs:index")
implementation project(":libs:database")
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.work:work-runtime:2.9.0'
implementation 'com.google.guava:guava:31.0-android' // somehow needed for work-runtime to function
implementation libs.androidx.appcompat
implementation libs.androidx.preference.ktx
implementation libs.androidx.gridlayout
implementation libs.androidx.recyclerview
implementation libs.androidx.cardview
implementation libs.androidx.vectordrawable
implementation libs.androidx.constraintlayout
implementation libs.androidx.lifecycle.livedata.ktx
implementation libs.androidx.palette.ktx
implementation libs.androidx.work.runtime
implementation libs.guava // somehow needed for work-runtime to function
implementation 'com.google.android.material:material:1.11.0'
implementation libs.material
//noinspection UseTomlInstead
implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false }
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24 or library desugering
implementation 'info.guardianproject.netcipher:netcipher:2.2.0-alpha'
//noinspection GradleDependency -> Commons IO > 2.5 uses java.nio.file, which requires desugaring
implementation 'commons-io:commons-io:2.6'
implementation 'commons-net:commons-net:3.6'
implementation 'ch.acra:acra-mail:5.11.3'
implementation 'ch.acra:acra-dialog:5.11.3'
implementation 'com.hannesdorfmann:adapterdelegates4:4.3.2'
implementation 'org.slf4j:slf4j-api:2.0.7'
implementation 'com.github.tony19:logback-android:3.0.0'
implementation libs.zxing.core
implementation libs.guardianproject.netcipher
implementation libs.commons.io
implementation libs.commons.net
implementation libs.acra.mail
implementation libs.acra.dialog
implementation libs.adapterdelegates4
implementation libs.slf4j.api
implementation libs.logback.android
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
implementation 'io.reactivex.rxjava3:rxjava:3.1.7'
implementation libs.rxjava
implementation libs.rxandroid
implementation "com.github.bumptech.glide:glide:4.16.0"
implementation("com.github.bumptech.glide:compose:1.0.0-alpha.3") {
exclude group: "androidx.test"
}
annotationProcessor "com.github.bumptech.glide:compiler:4.14.2"
implementation libs.glide
implementation(libs.glide.compose)
annotationProcessor libs.glide.compiler
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'org.bouncycastle:bcprov-jdk15to18:1.71'
fullImplementation 'info.guardianproject.panic:panic:1.0'
fullImplementation 'org.bouncycastle:bcpkix-jdk15to18:1.71'
fullImplementation 'org.jmdns:jmdns:3.5.5'
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation libs.okhttp
implementation libs.bcprov.jdk15to18
fullImplementation libs.guardianproject.panic
fullImplementation libs.bcpkix.jdk15to18
fullImplementation libs.jmdns
fullImplementation libs.nanohttpd
// newer compose-bom versions have an issue with app details repo dropdown
implementation platform('androidx.compose:compose-bom:2023.10.01')
implementation 'androidx.compose.material:material'
implementation 'androidx.compose.material:material-icons-extended'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose"
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.activity:activity-compose:1.8.2'
implementation "com.google.accompanist:accompanist-themeadapter-material:0.30.1"
debugImplementation 'androidx.compose.ui:ui-tooling'
implementation platform(libs.androidx.compose.bom)
implementation libs.androidx.compose.material
implementation libs.androidx.compose.material.icons.extended
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.compose.ui.tooling.preview
implementation libs.androidx.activity.compose
implementation libs.accompanist.themeadapter.material
implementation libs.accompanist.drawablepainter
debugImplementation libs.androidx.compose.ui.tooling
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.11.1'
testImplementation 'org.mockito:mockito-core:3.9.0'
testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation libs.androidx.test.core
testImplementation libs.junit
testImplementation libs.robolectric
testImplementation libs.mockito.core
testImplementation libs.hamcrest
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.arch.core:core-testing:2.2.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:monitor:1.6.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
androidTestImplementation 'androidx.work:work-testing:2.9.0'
androidTestImplementation 'org.jetbrains.kotlin:kotlin-test'
androidTestImplementation 'app.cash.turbine:turbine:1.0.0'
androidTestImplementation libs.androidx.test.core
androidTestImplementation libs.androidx.core.testing
androidTestImplementation libs.androidx.test.runner
androidTestImplementation libs.androidx.test.rules
androidTestImplementation libs.androidx.test.ext.junit
androidTestImplementation libs.androidx.test.monitor
androidTestImplementation libs.androidx.espresso.core
androidTestImplementation libs.androidx.test.uiautomator
androidTestImplementation libs.androidx.work.testing
androidTestImplementation libs.kotlin.test
androidTestImplementation libs.turbine
}
// org.fdroid.fdroid.updater.UpdateServiceTest needs app-full-debug.apk
@@ -255,5 +249,3 @@ android.productFlavors.all { flavor ->
}
}
}
apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"

View File

@@ -166,5 +166,4 @@ internal class RepoManagerAddAllIntegrationTest {
log.info(" final: $item")
return item
}
}

View File

@@ -22,6 +22,8 @@ import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.Nullable;
import org.fdroid.fdroid.BuildConfig;
import java.io.BufferedReader;
@@ -41,8 +43,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/**
* WifiApControl provides control over Wi-Fi APs using the singleton pattern.
* Even though isSupported should be reliable, the underlying hidden APIs that

View File

@@ -35,8 +35,6 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import com.google.common.collect.Lists;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.DBHelper;
import org.fdroid.fdroid.installer.PrivilegedInstaller;
@@ -45,6 +43,7 @@ import org.fdroid.fdroid.net.ConnectivityMonitorService;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -606,7 +605,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
HashMap<String, List<String>> output = new HashMap<String, List<String>>();
for (String line : string.split("\n")) {
String[] items = line.split(" ");
List<String> list = Lists.newArrayList(items);
ArrayList<String> list = new ArrayList<>(Arrays.asList(items));
String key = list.remove(0);
output.put(key, list);
}

View File

@@ -62,7 +62,8 @@ object ComposeUtils {
// set caption style to match MDC
caption = it.caption.copy(
color = colorResource(id = R.color.fdroid_caption),
fontSize = 12.sp)
fontSize = 12.sp,
)
)
} ?: MaterialTheme.typography,
shapes = shapes ?: MaterialTheme.shapes

View File

@@ -51,7 +51,8 @@ class IpfsGatewayAddActivity : AppCompatActivity() {
setContent {
FDroidContent {
IpfsGatewayAddScreen(onBackClicked = { onBackPressedDispatcher.onBackPressed() },
IpfsGatewayAddScreen(
onBackClicked = { onBackPressedDispatcher.onBackPressed() },
onAddUserGateway = { url ->
// don't allow adding default gateways to the user gateways list
if (!Preferences.DEFAULT_IPFS_GATEWAYS.contains(url)) {
@@ -63,7 +64,8 @@ class IpfsGatewayAddActivity : AppCompatActivity() {
}
}
finish()
})
},
)
}
}
}
@@ -78,24 +80,24 @@ fun IpfsGatewayAddScreen(
val focusRequester = remember { FocusRequester() }
var errorMsg by remember { mutableStateOf("") }
Scaffold(topBar = {
TopAppBar(
elevation = 4.dp,
backgroundColor = MaterialTheme.colors.primarySurface,
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(Icons.Filled.ArrowBack, stringResource(R.string.back))
}
},
title = {
Text(
text = stringResource(R.string.ipfsgw_add_title),
modifier = Modifier.alpha(ContentAlpha.high),
)
},
)
}
Scaffold(
topBar = {
TopAppBar(
elevation = 4.dp,
backgroundColor = MaterialTheme.colors.primarySurface,
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(Icons.Filled.ArrowBack, stringResource(R.string.back))
}
},
title = {
Text(
text = stringResource(R.string.ipfsgw_add_title),
modifier = Modifier.alpha(ContentAlpha.high),
)
},
)
},
) { paddingValues ->
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),

View File

@@ -59,8 +59,10 @@ class IpfsGatewaySettingsActivity : AppCompatActivity() {
setContent {
FDroidContent {
IpfsGatewaySettingsScreen(prefs = prefs,
onBackClicked = { onBackPressedDispatcher.onBackPressed() })
IpfsGatewaySettingsScreen(
prefs = prefs,
onBackClicked = { onBackPressedDispatcher.onBackPressed() },
)
}
}
}
@@ -95,13 +97,16 @@ fun IpfsGatewaySettingsScreen(
floatingActionButton = {
// it doesn't seam to be supported to disable FABs, so just hide it for now.
if (ipfsEnabled) {
FloatingActionButton(onClick = {
context.startActivity(Intent(context, IpfsGatewayAddActivity::class.java))
}) {
FloatingActionButton(
onClick = {
context.startActivity(Intent(context, IpfsGatewayAddActivity::class.java))
},
) {
Icon(Icons.Filled.Add, stringResource(id = R.string.ipfsgw_add_add))
}
}
}) { paddingValues ->
},
) { paddingValues ->
Box(
modifier = Modifier
.padding(paddingValues)
@@ -219,7 +224,9 @@ fun UserGatewaysSettings(
userGateways = newGateways
prefs.ipfsGwUserList = newGateways
}, enabled = ipfsEnabled, modifier = Modifier.align(Alignment.CenterVertically)
},
enabled = ipfsEnabled,
modifier = Modifier.align(Alignment.CenterVertically),
) {
Icon(
Icons.Default.DeleteForever,
@@ -234,7 +241,6 @@ fun UserGatewaysSettings(
@Composable
@Preview
fun IpfsGatewaySettingsScreenPreview() {
val prefs = object : IPreferencesIpfs {
override fun isIpfsEnabled(): Boolean = true
override fun setIpfsEnabled(enabled: Boolean) = throw NotImplementedError()

View File

@@ -121,14 +121,18 @@ fun RepoPreviewHeader(
val buttonAction: () -> Unit = when (val res = state.fetchResult) {
is IsNewRepository, is IsNewRepoAndNewMirror, is IsNewMirror -> onAddRepo
// unfortunately we need to duplicate these functions
is IsExistingRepository -> { ->
val repoId = res.existingRepoId
RepoDetailsActivity.launch(context, repoId)
is IsExistingRepository -> {
{
val repoId = res.existingRepoId
RepoDetailsActivity.launch(context, repoId)
}
}
is IsExistingMirror -> { ->
val repoId = res.existingRepoId
RepoDetailsActivity.launch(context, repoId)
is IsExistingMirror -> {
{
val repoId = res.existingRepoId
RepoDetailsActivity.launch(context, repoId)
}
}
else -> error("Unexpected fetch state: ${state.fetchResult}")

View File

@@ -101,7 +101,9 @@ class RepoUpdateWorker(
.setConstraints(constraints)
.build()
workManager.enqueueUniquePeriodicWork(
UNIQUE_WORK_NAME_AUTO_UPDATE, UPDATE, workRequest
UNIQUE_WORK_NAME_AUTO_UPDATE,
UPDATE,
workRequest,
)
} else {
Log.w(TAG, "Not scheduling job due to settings!")

View File

@@ -34,9 +34,9 @@ public class ApkTest {
@Before
public final void setUp() {
ShadowMimeTypeMap mimeTypeMap = Shadows.shadowOf(MimeTypeMap.getSingleton());
mimeTypeMap.addExtensionMimeTypMapping("apk", "application/vnd.android.package-archive");
mimeTypeMap.addExtensionMimeTypMapping("obf", "application/octet-stream");
mimeTypeMap.addExtensionMimeTypMapping("zip", PublicSourceDirProvider.SHARE_APK_MIME_TYPE);
mimeTypeMap.addExtensionMimeTypeMapping("apk", "application/vnd.android.package-archive");
mimeTypeMap.addExtensionMimeTypeMapping("obf", "application/octet-stream");
mimeTypeMap.addExtensionMimeTypeMapping("zip", PublicSourceDirProvider.SHARE_APK_MIME_TYPE);
ShadowLog.stream = System.out;
}

View File

@@ -3,12 +3,16 @@ buildscript {
mavenCentral()
maven { url 'https://maven.google.com/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:8.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.9.10"
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0'
}
}
plugins {
alias libs.plugins.android.application apply false
alias libs.plugins.jetbrains.kotlin.android apply false
alias libs.plugins.jetbrains.kotlin.multiplatform apply false
alias libs.plugins.jetbrains.kotlin.plugin.serialization apply false
alias libs.plugins.jetbrains.compose.compiler apply false
alias libs.plugins.jetbrains.dokka apply false
alias libs.plugins.jlleitschuh.ktlint apply false
alias libs.plugins.vanniktech.maven.publish apply false
}
allprojects {
repositories {
@@ -22,3 +26,13 @@ allprojects {
}
}
}
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
ktlint {
version = "1.3.1"
android = true
enableExperimentalRules = false
verbose = true
}
}

View File

@@ -1,11 +0,0 @@
ktlint {
version = "0.45.2"
android = true
enableExperimentalRules = false
verbose = true
disabledRules = [
"wrapping",
"import-ordering",
"no-blank-line-before-rbrace",
]
}

171
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,171 @@
[versions]
compileSdk = "35"
kotlin = "2.0.20"
androidGradlePlugin = "8.6.0"
dokka = "1.9.20"
mavenPublish = "0.18.0"
jlleitschuhKtlint = "12.1.1"
kotlinxSerializationJson = "1.4.1" # 1.4.1 because https://github.com/Kotlin/kotlinx.serialization/issues/2231
kotlinxCoroutinesTest = "1.7.3"
ktor = "2.3.12"
okhttp = "4.12.0"
room = "2.6.1"
glide = "4.16.0"
glideCompose = "1.0.0-beta01"
androidxCoreKtx = "1.13.1"
androidxAppcompat = "1.7.0"
androidxPreferenceKtx = "1.2.1"
androidxLifecycleLivedataKtx = "2.8.6"
androidxWork = "2.9.1"
androidxRecyclerview = "1.3.2"
androidxConstraintlayout = "2.1.4"
androidxCardview = "1.0.0"
androidxPaletteKtx = "1.0.0"
androidxVectordrawable = "1.2.0"
androidxGridlayout = "1.0.0"
androidxComposeBom = "2024.09.03"
androidxActivityCompose = "1.9.2"
accompanistThemeadapterMaterial = "0.30.1"
accompanistDrawablepainter = "0.36.0"
material = "1.12.0"
zxingCore = "3.3.3" # newer version need minSdk 24 or library desugering
guardianprojectNetcipher = "2.2.0-alpha"
guardianprojectPanic = "1.0"
acra = "5.11.3"
adapterdelegates4 = "4.3.2"
#noinspection GradleDependency Commons IO > 2.5 uses java.nio.file, which requires desugaring
commonsIo = "2.6"
commonsNet = "3.6"
bouncycastle = "1.71"
jmdns = "3.5.5"
nanohttpd = "2.3.1"
guava = "32.1.3-android"
rxjava = "3.1.9"
rxandroid = "3.0.2"
slf4jApi = "2.0.16"
microutilsKotlinLogging = "2.1.21"
logbackClassic = "1.5.6"
logbackAndroid = "3.0.0"
junit = "4.13.2"
mockk = "1.13.8"
robolectric = "4.12.2"
androidxTestCore = "1.6.1"
androidxTestRunner = "1.6.2"
androidxTestExtJunit = "1.2.1"
androidxCoreTesting = "2.2.0"
androidxTestCoreKtx = "1.6.1"
androidxEspressoCore = "3.6.1"
androidxTestRules = "1.6.1"
androidxTestUiautomator = "2.3.0"
androidxTestMonitor = "1.7.2"
mockitoCore = "5.1.1"
hamcrest = "2.2"
goncalossilvaResources = "0.2.1"
turbine = "1.0.0"
json = "20220320"
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidxCoreKtx" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidxLifecycleLivedataKtx" }
androidx-cardview = { module = "androidx.cardview:cardview", version.ref = "androidxCardview" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidxConstraintlayout" }
androidx-gridlayout = { module = "androidx.gridlayout:gridlayout", version.ref = "androidxGridlayout" }
androidx-palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "androidxPaletteKtx" }
androidx-preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "androidxPreferenceKtx" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppcompat" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "androidxRecyclerview" }
androidx-vectordrawable = { module = "androidx.vectordrawable:vectordrawable", version.ref = "androidxVectordrawable" }
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "androidxWork" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose" }
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
androidx-compose-material = { module = "androidx.compose.material:material" }
accompanist-themeadapter-material = { module = "com.google.accompanist:accompanist-themeadapter-material", version.ref = "accompanistThemeadapterMaterial" }
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanistDrawablepainter" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivityCompose" }
material = { module = "com.google.android.material:material", version.ref = "material" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
glide-compose = { module = "com.github.bumptech.glide:compose", version.ref = "glideCompose" }
glide-annotations = { module = "com.github.bumptech.glide:annotations", version.ref = "glide" }
guardianproject-netcipher = { module = "info.guardianproject.netcipher:netcipher", version.ref = "guardianprojectNetcipher" }
guardianproject-panic = { module = "info.guardianproject.panic:panic", version.ref = "guardianprojectPanic" }
nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" }
jmdns = { module = "org.jmdns:jmdns", version.ref = "jmdns" }
zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" }
acra-mail = { module = "ch.acra:acra-mail", version.ref = "acra" }
acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acra" }
adapterdelegates4 = { module = "com.hannesdorfmann:adapterdelegates4", version.ref = "adapterdelegates4" }
bcprov-jdk15to18 = { module = "org.bouncycastle:bcprov-jdk15to18", version.ref = "bouncycastle" }
bcpkix-jdk15to18 = { module = "org.bouncycastle:bcpkix-jdk15to18", version.ref = "bouncycastle" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logbackClassic" }
logback-android = { module = "com.github.tony19:logback-android", version.ref = "logbackAndroid" }
microutils-kotlin-logging = { module = "io.github.microutils:kotlin-logging", version.ref = "microutilsKotlinLogging" }
commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" }
commons-net = { module = "commons-net:commons-net", version.ref = "commonsNet" }
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
goncalossilva-resources = { module = "com.goncalossilva:resources", version.ref = "goncalossilvaResources" }
rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
junit = { module = "junit:junit", version.ref = "junit" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTestRunner" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidxTestCoreKtx" }
androidx-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "androidxCoreTesting" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspressoCore" }
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidxTestUiautomator" }
androidx-test-monitor = { module = "androidx.test:monitor", version.ref = "androidxTestMonitor" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidxTestRules" }
androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidxWork" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4jApi" }
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoCore" }
json = { module = "org.json:json", version.ref = "json" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
jetbrains-kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
jetbrains-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
jlleitschuh-ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "jlleitschuhKtlint" }

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=c16d517b50dd28b3f5838f0e844b7520b8f1eb610f2f29de7e4e04a1b7c9c79b
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
distributionSha256Sum=2ab88d6de2c23e6adae7363ae6e29cbdd2a709e992929b48b6530fd0c7133bd6
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -3,17 +3,16 @@ plugins {
id 'com.android.library'
id 'kotlin-kapt'
id 'org.jetbrains.dokka'
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
id 'com.vanniktech.maven.publish'
}
android {
namespace "org.fdroid.database"
compileSdk 34
compileSdk libs.versions.compileSdk.get().toInteger()
defaultConfig {
minSdkVersion 21
targetSdk 33 // relevant for instrumentation tests (targetSdk 21 fails on Android 14)
targetSdk 34 // relevant for instrumentation tests (targetSdk 21 fails on Android 14)
consumerProguardFiles "consumer-rules.pro"
javaCompileOptions {
@@ -57,6 +56,9 @@ android {
freeCompilerArgs += "-Xexplicit-api=strict"
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
kapt {
correctErrorTypes true
}
aaptOptions {
// needed only for instrumentation tests: assets.openFd()
noCompress "json"
@@ -73,41 +75,39 @@ dependencies {
implementation project(":libs:download")
implementation project(":libs:index")
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
implementation libs.androidx.core.ktx
implementation libs.androidx.lifecycle.livedata.ktx
implementation "androidx.room:room-runtime:2.6.1"
implementation "androidx.room:room-ktx:2.6.1"
kapt "androidx.room:room-compiler:2.6.1"
implementation libs.androidx.room.runtime
implementation libs.androidx.room.ktx
kapt libs.androidx.room.compiler
implementation 'io.github.microutils:kotlin-logging:2.1.21'
// 1.4.1 because https://github.com/Kotlin/kotlinx.serialization/issues/2231
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation libs.microutils.kotlin.logging
implementation libs.kotlinx.serialization.json
testImplementation project(":libs:sharedTest")
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.8'
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'androidx.arch.core:core-testing:2.2.0'
testImplementation "androidx.room:room-testing:2.6.1"
testImplementation 'org.robolectric:robolectric:4.11.1'
testImplementation 'commons-io:commons-io:2.6'
testImplementation 'ch.qos.logback:logback-classic:1.4.5'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'app.cash.turbine:turbine:1.0.0'
testImplementation 'com.squareup.okhttp3:okhttp:4.12.0'
testImplementation libs.junit
testImplementation libs.mockk
testImplementation libs.kotlin.test
testImplementation libs.androidx.test.core.ktx
testImplementation libs.androidx.test.ext.junit
testImplementation libs.androidx.core.testing
testImplementation libs.androidx.room.testing
testImplementation libs.robolectric
testImplementation libs.commons.io
testImplementation libs.logback.classic
testImplementation libs.kotlinx.coroutines.test
testImplementation libs.turbine
testImplementation libs.okhttp
androidTestImplementation project(":libs:sharedTest")
androidTestImplementation 'io.mockk:mockk-android:1.13.8'
androidTestImplementation 'org.jetbrains.kotlin:kotlin-test'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.arch.core:core-testing:2.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation "androidx.room:room-testing:2.6.1"
androidTestImplementation 'commons-io:commons-io:2.6'
androidTestImplementation libs.mockk.android
androidTestImplementation libs.kotlin.test
androidTestImplementation libs.androidx.test.ext.junit
androidTestImplementation libs.androidx.core.testing
androidTestImplementation libs.androidx.espresso.core
androidTestImplementation libs.androidx.room.testing
androidTestImplementation libs.commons.io
}
plugins.withId("kotlin-kapt") {
@@ -127,5 +127,3 @@ tasks.withType(DokkaTask).configureEach {
}"""]
)
}
apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"

View File

@@ -14,6 +14,7 @@ internal abstract class AppTest : DbTest() {
protected val name1 = mapOf("en-US" to "1")
protected val name2 = mapOf("en-US" to "2")
protected val name3 = mapOf("en-US" to "3")
// it is important for testing that the icons are sharing at least one locale
protected val icons1 = mapOf("en-US" to getRandomFileV2(), "bar" to getRandomFileV2())
protected val icons2 = mapOf("en-US" to getRandomFileV2(), "42" to getRandomFileV2())

View File

@@ -32,7 +32,7 @@ internal class DbUpdateCheckerTest : AppTest() {
private val compatChecker: (PackageVersionV2) -> Boolean = { true }
private val packageInfo = PackageInfo().apply {
packageName = TestDataMinV2.packageName
packageName = TestDataMinV2.PACKAGE_NAME
versionCode = 0
}
@@ -219,7 +219,7 @@ internal class DbUpdateCheckerTest : AppTest() {
val appVersions = updateChecker.getUpdatableApps()
assertEquals(1, appVersions.size)
assertEquals(0, appVersions[0].installedVersionCode)
assertEquals(TestDataMinV2.packageName, appVersions[0].packageName)
assertEquals(TestDataMinV2.PACKAGE_NAME, appVersions[0].packageName)
assertEquals(TestDataMinV2.version.file.sha256, appVersions[0].update.version.versionId)
}

View File

@@ -40,12 +40,10 @@ internal class FtsCaseInsensitiveMigrationTest {
private val repo = ContentValues().apply {
put("repoId", 1)
put("name", Converters.localizedTextV2toString(mapOf(
"de" to "a", "en-US" to "b")))
put("name", Converters.localizedTextV2toString(mapOf("de" to "a", "en-US" to "b")))
put("address", getRandomString())
put("certificate", "abcdef")
put("description", Converters.localizedTextV2toString(mapOf(
"de" to "aa", "en-US" to "bb")))
put("description", Converters.localizedTextV2toString(mapOf("de" to "aa", "en-US" to "bb")))
put("version", Random.nextLong())
put("timestamp", Random.nextLong())
}
@@ -59,14 +57,29 @@ internal class FtsCaseInsensitiveMigrationTest {
private val oeffiMetadata = ContentValues().apply {
put("packageName", "de.schildbach.oeffi")
put("repoId", 1)
put("name", Converters.localizedTextV2toString(mapOf(
"de" to "Öffi", "en-US" to "Offi")))
put("description", Converters.localizedTextV2toString(mapOf(
"de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport")))
put(
"name", Converters.localizedTextV2toString(
mapOf(
"de" to "Öffi", "en-US" to "Offi"
)
)
)
put(
"description", Converters.localizedTextV2toString(
mapOf(
"de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"
)
)
)
put("license", "GPL-3.0")
put("summary", Converters.localizedTextV2toString(mapOf(
"de" to "Der König des Fahrplandschungels!",
"en-US" to " King of public transit planning!")))
put(
"summary", Converters.localizedTextV2toString(
mapOf(
"de" to "Der König des Fahrplandschungels!",
"en-US" to " King of public transit planning!"
)
)
)
put("localizedName", "Öffi")
put("localizedSummary", "Der König des Fahrplandschungels!")
put("added", Random.nextLong())
@@ -89,17 +102,34 @@ internal class FtsCaseInsensitiveMigrationTest {
private val transportrMetadata = ContentValues().apply {
put("packageName", "de.grobox.liberario")
put("repoId", 1)
put("name", Converters.localizedTextV2toString(mapOf(
"de" to "Transportr", "en-US" to "Transportr")))
put("description", Converters.localizedTextV2toString(mapOf(
"de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport")))
put(
"name", Converters.localizedTextV2toString(
mapOf(
"de" to "Transportr", "en-US" to "Transportr"
)
)
)
put(
"description", Converters.localizedTextV2toString(
mapOf(
"de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"
)
)
)
put("license", "GPL-3.0")
put("summary", Converters.localizedTextV2toString(mapOf(
"de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking",
"en-US" to "Free Public Transport Assistant without Ads or Tracking")))
put(
"summary", Converters.localizedTextV2toString(
mapOf(
"de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung",
"en-US" to "Free Public Transport Assistant without Ads or Tracking"
)
)
)
put("localizedName", "Transportr")
put("localizedSummary",
"Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking")
put(
"localizedSummary",
"Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking"
)
put("added", Random.nextLong())
put("lastUpdated", Random.nextLong())
put("isCompatible", true)
@@ -119,7 +149,6 @@ internal class FtsCaseInsensitiveMigrationTest {
@Test
fun testMigration() = runBlocking {
helper.createDatabase(TEST_DB, 5).use { db ->
// Database has schema version 5. Insert some data using SQL queries.
// We can't use DAO classes because they expect the latest schema.

View File

@@ -282,8 +282,8 @@ internal class IndexV2DiffTest : DbTest() {
diff = diffRepoIdJson,
endIndex = TestDataMidV2.index.copy(
packages = mapOf(
TestDataMidV2.packageName1 to TestDataMidV2.app1,
TestDataMidV2.packageName2 to fdroidPackage,
TestDataMidV2.PACKAGE_NAME_1 to TestDataMidV2.app1,
TestDataMidV2.PACKAGE_NAME_2 to fdroidPackage,
)
),
)

View File

@@ -41,7 +41,8 @@ internal class RepoCertNonNullMigrationTest {
put("address", "https://example.org/repo")
put("certificate", "0123")
put("timestamp", -1)
})
},
)
db.insert(
RepositoryPreferences.TABLE,
SQLiteDatabase.CONFLICT_FAIL,
@@ -49,7 +50,8 @@ internal class RepoCertNonNullMigrationTest {
put("repoId", repoId1)
put("enabled", true)
put("weight", Long.MAX_VALUE)
})
},
)
val repoId2 = db.insert(
CoreRepository.TABLE,
SQLiteDatabase.CONFLICT_FAIL,
@@ -58,7 +60,8 @@ internal class RepoCertNonNullMigrationTest {
put("description", localizedTextV2toString(mapOf("en-US" to "no cert desc")))
put("address", "https://example.com/repo")
put("timestamp", -1)
})
},
)
db.insert(
RepositoryPreferences.TABLE,
SQLiteDatabase.CONFLICT_FAIL,
@@ -66,7 +69,8 @@ internal class RepoCertNonNullMigrationTest {
put("repoId", repoId2)
put("enabled", true)
put("weight", Long.MAX_VALUE - 2)
})
},
)
}
// Re-open the database with version 2, auto-migrations are applied automatically

View File

@@ -186,12 +186,14 @@ public data class App internal constructor(
localizedFileLists?.iterator()?.forEach { file ->
if (file.repoId != metadata.repoId || file.type != type) return@forEach
val list = map.getOrPut(file.locale) { ArrayList() } as ArrayList
list.add(FileV2(
name = file.name,
sha256 = file.sha256,
size = file.size,
ipfsCidV1 = file.ipfsCidV1,
))
list.add(
FileV2(
name = file.name,
sha256 = file.sha256,
size = file.size,
ipfsCidV1 = file.ipfsCidV1,
)
)
}
return map.ifEmpty { null }
}
@@ -418,8 +420,10 @@ internal fun List<IFile>.toLocalizedFileV2(): LocalizedFileV2? = associate { fil
// We can't restrict this query further (e.g. only from enabled repos or max weight),
// because we are using this via @Relation on packageName for specific repos.
// When filtering the result for only the repoId we are interested in, we'd get no icons.
@DatabaseView(viewName = LocalizedIcon.TABLE,
value = "SELECT * FROM ${LocalizedFile.TABLE} WHERE type='icon'")
@DatabaseView(
viewName = LocalizedIcon.TABLE,
value = "SELECT * FROM ${LocalizedFile.TABLE} WHERE type='icon'",
)
internal data class LocalizedIcon(
val repoId: Long,
val packageName: String,

View File

@@ -130,7 +130,8 @@ public interface AppDao {
}
public enum class AppListSortOrder {
LAST_UPDATED, NAME
LAST_UPDATED,
NAME,
}
/**

View File

@@ -32,7 +32,7 @@ public object FDroidDatabaseHolder {
// Singleton prevents multiple instances of database opening at the same time.
@Volatile
@GuardedBy("lock")
private var INSTANCE: FDroidDatabaseInt? = null
private var instance: FDroidDatabaseInt? = null
private val lock = Object()
internal val TAG = FDroidDatabase::class.simpleName
@@ -52,7 +52,7 @@ public object FDroidDatabaseHolder {
): FDroidDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(lock) {
return instance ?: synchronized(lock) {
val builder = Room.databaseBuilder(
context.applicationContext,
FDroidDatabaseInt::class.java,
@@ -66,7 +66,7 @@ public object FDroidDatabaseHolder {
if (fixture != null) addCallback(FixtureCallback(fixture))
}
val instance = builder.build()
INSTANCE = instance
this.instance = instance
// return instance
instance
}
@@ -79,7 +79,7 @@ public object FDroidDatabaseHolder {
GlobalScope.launch(dispatcher) {
val database: FDroidDatabase
synchronized(lock) {
database = INSTANCE ?: error("DB not yet initialized")
database = instance ?: error("DB not yet initialized")
}
fixture.prePopulateDb(database)
Log.d(TAG, "Loaded fixtures")

View File

@@ -182,7 +182,8 @@ internal interface RepositoryDaoInt : RepositoryDao {
address: String,
username: String? = null,
password: String? = null,
certificate: String = "6789" // just used for testing
// just used for testing
certificate: String = "6789",
): Long {
val repo = CoreRepository(
name = mapOf("en-US" to address),
@@ -234,9 +235,11 @@ internal interface RepositoryDaoInt : RepositoryDao {
* Returns a non-archive repository with the given [certificate], if it exists in the DB.
*/
@Transaction
@Query("""SELECT * FROM ${CoreRepository.TABLE}
@Query(
"""SELECT * FROM ${CoreRepository.TABLE}
WHERE certificate = :certificate AND address NOT LIKE "%/archive" COLLATE NOCASE
LIMIT 1""")
LIMIT 1"""
)
fun getRepository(certificate: String): Repository?
@Transaction
@@ -261,9 +264,11 @@ internal interface RepositoryDaoInt : RepositoryDao {
fun getRepositoryPreferences(repoId: Long): RepositoryPreferences?
@RewriteQueriesToDropUnusedColumns
@Query("""SELECT * FROM ${Category.TABLE}
@Query(
"""SELECT * FROM ${Category.TABLE}
JOIN ${RepositoryPreferences.TABLE} AS pref USING (repoId)
WHERE pref.enabled = 1 GROUP BY id HAVING MAX(pref.weight)""")
WHERE pref.enabled = 1 GROUP BY id HAVING MAX(pref.weight)"""
)
override fun getLiveCategories(): LiveData<List<Category>>
/**
@@ -365,16 +370,22 @@ internal interface RepositoryDaoInt : RepositoryDao {
@Query("UPDATE ${AppPrefs.TABLE} SET preferredRepoId = NULL WHERE preferredRepoId = :repoId")
fun resetPreferredRepoInAppPrefs(repoId: Long)
@Query("""UPDATE ${RepositoryPreferences.TABLE} SET userMirrors = :mirrors
WHERE repoId = :repoId""")
@Query(
"""UPDATE ${RepositoryPreferences.TABLE} SET userMirrors = :mirrors
WHERE repoId = :repoId"""
)
override fun updateUserMirrors(repoId: Long, mirrors: List<String>)
@Query("""UPDATE ${RepositoryPreferences.TABLE} SET username = :username, password = :password
WHERE repoId = :repoId""")
@Query(
"""UPDATE ${RepositoryPreferences.TABLE} SET username = :username, password = :password
WHERE repoId = :repoId"""
)
override fun updateUsernameAndPassword(repoId: Long, username: String?, password: String?)
@Query("""UPDATE ${RepositoryPreferences.TABLE} SET disabledMirrors = :disabledMirrors
WHERE repoId = :repoId""")
@Query(
"""UPDATE ${RepositoryPreferences.TABLE} SET disabledMirrors = :disabledMirrors
WHERE repoId = :repoId"""
)
override fun updateDisabledMirrors(repoId: Long, disabledMirrors: List<String>)
/**

View File

@@ -147,9 +147,11 @@ internal fun ManifestV2.toManifest() = AppManifest(
features = features.map { it.name },
)
@DatabaseView(viewName = HighestVersion.TABLE,
@DatabaseView(
viewName = HighestVersion.TABLE,
value = """SELECT repoId, packageName, antiFeatures FROM ${Version.TABLE}
GROUP BY repoId, packageName HAVING MAX(manifest_versionCode)""")
GROUP BY repoId, packageName HAVING MAX(manifest_versionCode)""",
)
internal class HighestVersion(
val repoId: Long,
val packageName: String,
@@ -230,7 +232,9 @@ private fun <T> VersionedString.map(
wantedType: VersionedStringType,
factory: () -> T,
): T? {
return if (repoId != v.repoId || packageName != v.packageName || versionId != v.versionId ||
return if (repoId != v.repoId ||
packageName != v.packageName ||
versionId != v.versionId ||
type != wantedType
) null
else factory()

View File

@@ -433,7 +433,8 @@ internal class RepoAdderTest {
coEvery {
httpManager.getDigestInputStream(match {
it.indexFile.name == "../index-min-v2.json" &&
it.mirrors.size == 1 && it.mirrors[0].baseUrl == urlTrimmed
it.mirrors.size == 1 &&
it.mirrors[0].baseUrl == urlTrimmed
})
} returns indexStream
every {
@@ -550,7 +551,8 @@ internal class RepoAdderTest {
coEvery {
httpManager.getDigestInputStream(match {
it.indexFile.name == "../index-min-v2.json" &&
it.mirrors.size == 1 && it.mirrors[0].baseUrl == repoAddress
it.mirrors.size == 1 &&
it.mirrors[0].baseUrl == repoAddress
})
} returns indexStream
every {
@@ -610,7 +612,8 @@ internal class RepoAdderTest {
coEvery {
httpManager.getDigestInputStream(match {
it.indexFile.name == "/index-v2.json" &&
it.mirrors.size == 1 && it.mirrors[0].baseUrl == "https://example.org/repo"
it.mirrors.size == 1 &&
it.mirrors[0].baseUrl == "https://example.org/repo"
})
} returns indexStream
every {
@@ -776,7 +779,8 @@ internal class RepoAdderTest {
coEvery {
httpManager.getDigestInputStream(match {
it.indexFile.name == "../index-min-v2.json" &&
it.mirrors.size == 1 && it.mirrors[0].baseUrl == urlTrimmed
it.mirrors.size == 1 &&
it.mirrors[0].baseUrl == urlTrimmed
})
} returns indexStream
every {
@@ -803,7 +807,8 @@ internal class RepoAdderTest {
it.address == TestDataMinV2.repo.address &&
it.formatVersion == IndexFormatVersion.TWO &&
it.name.getBestLocale(localeList) == repoName &&
it.username == username && it.password == password // this is the important bit
it.username == username &&
it.password == password // this is the important bit
})
} returns 42L
every { repoDao.updateUserMirrors(42L, listOf(urlTrimmed)) } just Runs
@@ -850,7 +855,8 @@ internal class RepoAdderTest {
coEvery {
httpManager.getDigestInputStream(match {
it.indexFile.name == "../index-min-v2.json" &&
it.mirrors.size == 1 && it.mirrors[0].baseUrl == urlTrimmed
it.mirrors.size == 1 &&
it.mirrors[0].baseUrl == urlTrimmed
})
} returns indexStream
every {
@@ -895,7 +901,7 @@ internal class RepoAdderTest {
val state3 = awaitItem()
assertIs<Fetching>(state3)
assertEquals(TestDataMinV2.packages.size, state3.apps.size)
assertEquals(TestDataMinV2.packageName, state3.apps[0].packageName)
assertEquals(TestDataMinV2.PACKAGE_NAME, state3.apps[0].packageName)
assertFalse(state3.done)
// final result

View File

@@ -2,7 +2,6 @@ plugins {
id 'org.jetbrains.kotlin.multiplatform'
id 'com.android.library'
id 'org.jetbrains.dokka'
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
id 'com.vanniktech.maven.publish'
}
@@ -30,57 +29,56 @@ kotlin {
}
commonMain {
dependencies {
api "io.ktor:ktor-client-core:2.3.8"
implementation 'io.github.microutils:kotlin-logging:2.1.21'
api libs.ktor.client.core
implementation libs.microutils.kotlin.logging
}
}
commonTest {
dependencies {
implementation kotlin('test')
implementation "io.ktor:ktor-client-mock:2.3.8"
implementation libs.ktor.client.mock
}
}
// JVM is disabled for now, because Android app is including it instead of Android library
jvmMain {
dependencies {
implementation "io.ktor:ktor-client-cio:2.3.8"
implementation libs.ktor.client.cio
}
}
jvmTest {
dependencies {
implementation 'junit:junit:4.13.2'
implementation libs.junit
}
}
androidMain {
// needed because of https://issuetracker.google.com/issues/231701341
kotlin.srcDir("src/commonMain/kotlin")
dependencies {
implementation "io.ktor:ktor-client-okhttp:2.3.8"
implementation libs.ktor.client.okhttp
//noinspection UseTomlInstead
implementation("com.github.bumptech.glide:glide:4.16.0") {
transitive = false // we don't need all that it pulls in, just the basics
}
implementation "com.github.bumptech.glide:annotations:4.16.0"
implementation libs.glide.annotations
}
}
androidUnitTest {
dependencies {
implementation kotlin('test')
implementation 'org.json:json:20220320'
implementation 'junit:junit:4.13.2'
implementation 'ch.qos.logback:logback-classic:1.4.5'
implementation libs.json
implementation libs.junit
implementation libs.logback.classic
}
}
androidInstrumentedTest {
dependsOn(commonTest)
dependencies {
implementation project(":libs:sharedTest")
implementation 'androidx.test:runner:1.5.2'
implementation 'androidx.test.ext:junit:1.1.5'
implementation libs.androidx.test.runner
implementation libs.androidx.test.ext.junit
}
}
nativeMain {
dependencies {
implementation "io.ktor:ktor-client-curl:2.3.8"
implementation libs.ktor.client.curl
}
}
nativeTest {
@@ -91,7 +89,7 @@ kotlin {
android {
namespace "org.fdroid.download"
compileSdk 34
compileSdk libs.versions.compileSdk.get().toInteger()
sourceSets {
main.manifest.srcFile('src/androidMain/AndroidManifest.xml')
getByName("androidTest").java.srcDir(file("src/androidAndroidTest/kotlin"))
@@ -131,5 +129,3 @@ tasks.withType(DokkaTask).configureEach {
}"""]
)
}
apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"

View File

@@ -226,7 +226,3 @@ public abstract class Downloader constructor(
}
}
public fun interface BytesReceiver {
public suspend fun receive(bytes: ByteArray, numTotalBytes: Long?)
}

View File

@@ -246,6 +246,10 @@ public open class HttpManager @JvmOverloads constructor(
}
}
public fun interface BytesReceiver {
public suspend fun receive(bytes: ByteArray, numTotalBytes: Long?)
}
/**
* Thrown if we tried to resume a download, but the current mirror server does not offer resuming.
*/

View File

@@ -1,14 +1,13 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22'
id 'org.jetbrains.kotlin.plugin.serialization'
id 'com.android.library'
id 'org.jetbrains.dokka'
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
id 'com.vanniktech.maven.publish'
}
kotlin {
android {
androidTarget {
compilations.configureEach {
kotlinOptions.jvmTarget = '17'
}
@@ -33,19 +32,18 @@ kotlin {
}
commonMain {
dependencies {
// 1.4.1 because https://github.com/Kotlin/kotlinx.serialization/issues/2231
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation 'io.github.microutils:kotlin-logging:2.1.21'
implementation libs.kotlinx.serialization.json
implementation libs.microutils.kotlin.logging
implementation project(":libs:download")
implementation "io.ktor:ktor-io:2.3.8"
implementation libs.ktor.io
}
}
commonTest {
dependencies {
implementation project(":libs:sharedTest")
implementation kotlin('test')
implementation "com.goncalossilva:resources:0.2.1"
implementation libs.goncalossilva.resources
}
}
// JVM is disabled for now, because Android app is including it instead of Android library
@@ -55,27 +53,27 @@ kotlin {
}
jvmTest {
dependencies {
implementation 'junit:junit:4.13.2'
implementation libs.junit
}
}
androidMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.22"
implementation 'androidx.core:core-ktx:1.12.0'
implementation libs.kotlin.reflect
implementation libs.androidx.core.ktx
}
}
androidUnitTest {
dependencies {
implementation 'junit:junit:4.13.2'
implementation 'io.mockk:mockk:1.13.8'
implementation libs.junit
implementation libs.mockk
}
}
androidInstrumentedTest {
dependencies {
implementation project(":libs:sharedTest")
implementation kotlin('test')
implementation 'androidx.test:runner:1.5.2'
implementation 'androidx.test.ext:junit:1.1.5'
implementation libs.androidx.test.runner
implementation libs.androidx.test.ext.junit
}
}
nativeMain {
@@ -90,7 +88,7 @@ kotlin {
android {
namespace "org.fdroid.index"
compileSdk 34
compileSdk libs.versions.compileSdk.get().toInteger()
sourceSets {
main.manifest.srcFile('src/androidMain/AndroidManifest.xml')
getByName("androidTest").java.srcDir(file("src/androidAndroidTest/kotlin"))
@@ -124,5 +122,3 @@ tasks.withType(DokkaTask).configureEach {
}"""]
)
}
apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"

View File

@@ -74,10 +74,12 @@ internal class BestLocaleTest {
// handle stripped script (Hans/Hant)
assertEquals(
"zh-TW",
getMap("en-US",
getMap(
"en-US",
"zh-CN",
"zh-HK",
"zh-TW").getBestLocale(getLocaleList("zh-Hant-TW,zh-Hans-CN")),
"zh-TW",
).getBestLocale(getLocaleList("zh-Hant-TW,zh-Hans-CN")),
)
assertEquals(
"zh-CN",

View File

@@ -99,7 +99,8 @@ public class UpdateChecker(
versions.iterator().forEach versions@{ version ->
// if the installed version has a known vulnerability, we return it as well
if (includeKnownVulnerabilities &&
version.versionCode == installedVersionCode && version.hasKnownVulnerability
version.versionCode == installedVersionCode &&
version.hasKnownVulnerability
) return version
// if version code is not higher than installed skip package as list is sorted
if (version.versionCode <= installedVersionCode) return null

View File

@@ -87,10 +87,10 @@ public class IndexV1StreamProcessor(
private fun deserializeRepo(decoder: JsonDecoder, index: Int) {
require(index == descriptor.getElementIndex("repo"))
val repo = decoder.decodeSerializableValue(RepoV1.serializer())
if (lastTimestamp >= repo.timestamp) {
throw OldIndexException(lastTimestamp == repo.timestamp,
"Old repo ${repo.address} ${repo.timestamp}")
}
if (lastTimestamp >= repo.timestamp) throw OldIndexException(
isSameTimestamp = lastTimestamp == repo.timestamp,
msg = "Old repo ${repo.address} ${repo.timestamp}",
)
val repoV2 = repo.toRepoV2(
locale = DEFAULT_LOCALE,
antiFeatures = emptyMap(),
@@ -165,7 +165,9 @@ public class IndexV1StreamProcessor(
val packageIndex = compositeDecoder.decodeElementIndex(descriptor)
if (packageIndex == DECODE_DONE) break
val packageVersionV1 = decoder.decodeSerializableElement(
descriptor, index + 1, PackageV1.serializer()
descriptor = descriptor,
index = index + 1,
deserializer = PackageV1.serializer(),
)
val versionCode = packageVersionV1.versionCode ?: 0
val suggestedVersionCode =

View File

@@ -49,11 +49,13 @@ public class IndexV2DiffStreamProcessor(
val index = decoder.decodeElementIndex(descriptor)
if (index == packagesIndex) diffPackages(decoder, index)
}
packagesIndex -> {
diffPackages(decoder, startIndex)
val index = decoder.decodeElementIndex(descriptor)
if (index == repoIndex) diffRepo(version, decoder, index)
}
else -> error("Unexpected startIndex: $startIndex")
}
var currentIndex = 0
@@ -89,7 +91,9 @@ public class IndexV2DiffStreamProcessor(
val packageName = decoder.decodeStringElement(descriptor, index)
decoder.decodeElementIndex(descriptor)
val packageV2 = decoder.decodeSerializableElement(
descriptor, index + 1, JsonElement.serializer()
descriptor = descriptor,
index = index + 1,
deserializer = JsonElement.serializer(),
)
if (packageV2 is JsonNull) {
// delete app and existing metadata

View File

@@ -88,7 +88,9 @@ public class IndexV2FullStreamProcessor(
val packageName = decoder.decodeStringElement(descriptor, index)
decoder.decodeElementIndex(descriptor)
val packageV2 = decoder.decodeSerializableElement(
descriptor, index + 1, PackageV2.serializer()
descriptor = descriptor,
index = index + 1,
deserializer = PackageV2.serializer(),
)
indexStreamReceiver.receive(packageName, packageV2)
}

View File

@@ -1,7 +1,7 @@
package org.fdroid.index.v1
import kotlinx.serialization.SerializationException
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.fdroid.index.v2.AntiFeatureV2
import org.fdroid.index.v2.CategoryV2
import org.fdroid.index.v2.IndexV2
@@ -29,34 +29,43 @@ internal class IndexV1StreamProcessorTest {
@Test
fun testEmpty() {
testStreamProcessing("$assetPath/index-empty-v1.json", TestDataEmptyV2.index.v1compat())
testStreamProcessing("$ASSET_PATH/index-empty-v1.json", TestDataEmptyV2.index.v1compat())
}
@Test(expected = OldIndexException::class)
fun testEmptyEqualTimestamp() {
testStreamProcessing("$assetPath/index-empty-v1.json",
TestDataEmptyV2.index.v1compat(), TestDataEmptyV2.index.repo.timestamp)
testStreamProcessing(
"$ASSET_PATH/index-empty-v1.json",
TestDataEmptyV2.index.v1compat(),
TestDataEmptyV2.index.repo.timestamp,
)
}
@Test(expected = OldIndexException::class)
fun testEmptyHigherTimestamp() {
testStreamProcessing("$assetPath/index-empty-v1.json",
TestDataEmptyV2.index.v1compat(), TestDataEmptyV2.index.repo.timestamp + 1)
testStreamProcessing(
"$ASSET_PATH/index-empty-v1.json",
TestDataEmptyV2.index.v1compat(),
TestDataEmptyV2.index.repo.timestamp + 1,
)
}
@Test
fun testMin() {
testStreamProcessing("$assetPath/index-min-v1.json", TestDataMinV2.index.v1compat())
testStreamProcessing(
"$ASSET_PATH/index-min-v1.json",
TestDataMinV2.index.v1compat(),
)
}
@Test
fun testMid() {
testStreamProcessing("$assetPath/index-mid-v1.json", TestDataMidV2.indexCompat)
testStreamProcessing("$ASSET_PATH/index-mid-v1.json", TestDataMidV2.indexCompat)
}
@Test
fun testMax() {
testStreamProcessing("$assetPath/index-max-v1.json", TestDataMaxV2.indexCompat)
testStreamProcessing("$ASSET_PATH/index-max-v1.json", TestDataMaxV2.indexCompat)
}
@Test
@@ -73,16 +82,20 @@ internal class IndexV1StreamProcessorTest {
// empty repo dict
assertFailsWith<SerializationException> {
testStreamError("""{
testStreamError(
"""{
"repo": {}
}""".trimIndent())
}""".trimIndent()
)
}.also { assertContains(it.message!!, "timestamp") }
// timestamp not a number
assertFailsWith<SerializationException> {
testStreamError("""{
testStreamError(
"""{
"repo": { "timestamp": "string" }
}""".trimIndent())
}""".trimIndent()
)
}.also { assertContains(it.message!!, "numeric literal") }
// remember valid repo for further tests
@@ -99,21 +112,25 @@ internal class IndexV1StreamProcessorTest {
// apps is dict
assertFailsWith<SerializationException> {
testStreamError("""{
testStreamError(
"""{
$validRepo,
"requests": {"install": [], "uninstall": []},
"apps": {}
}""".trimIndent())
}""".trimIndent()
)
}.also { assertContains(it.message!!, "apps") }
// packages is list
assertFailsWith<SerializationException> {
testStreamError("""{
testStreamError(
"""{
$validRepo,
"requests": {"install": [], "uninstall": []},
"apps": [],
"packages": []
}""".trimIndent())
}""".trimIndent()
)
}.also { assertContains(it.message!!, "packages") }
}

View File

@@ -2,7 +2,7 @@ package org.fdroid.index.v2
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.fdroid.test.TestDataEmptyV2
import org.fdroid.test.TestDataMaxV2
import org.fdroid.test.TestDataMidV2
@@ -27,27 +27,27 @@ internal class IndexV2FullStreamProcessorTest {
@Test
fun testEmpty() {
testStreamProcessing("$assetPath/index-empty-v2.json", TestDataEmptyV2.index, 0)
testStreamProcessing("$ASSET_PATH/index-empty-v2.json", TestDataEmptyV2.index, 0)
}
@Test
fun testMin() {
testStreamProcessing("$assetPath/index-min-v2.json", TestDataMinV2.index, 1)
testStreamProcessing("$ASSET_PATH/index-min-v2.json", TestDataMinV2.index, 1)
}
@Test
fun testMinReordered() {
testStreamProcessing("$assetPath/index-min-reordered-v2.json", TestDataMinV2.index, 1)
testStreamProcessing("$ASSET_PATH/index-min-reordered-v2.json", TestDataMinV2.index, 1)
}
@Test
fun testMid() {
testStreamProcessing("$assetPath/index-mid-v2.json", TestDataMidV2.index, 2)
testStreamProcessing("$ASSET_PATH/index-mid-v2.json", TestDataMidV2.index, 2)
}
@Test
fun testMax() {
testStreamProcessing("$assetPath/index-max-v2.json", TestDataMaxV2.index, 3)
testStreamProcessing("$ASSET_PATH/index-max-v2.json", TestDataMaxV2.index, 3)
}
@Test

View File

@@ -8,7 +8,7 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import org.fdroid.index.IndexParser
import org.fdroid.index.IndexParser.json
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.fdroid.index.parseV2
import org.fdroid.test.DiffUtils.clean
import org.fdroid.test.DiffUtils.cleanMetadata
@@ -27,44 +27,44 @@ internal class ReflectionDifferTest {
@Test
fun testEmptyToMin() = testDiff(
diffPath = "$assetPath/diff-empty-min/23.json",
startPath = "$assetPath/index-empty-v2.json",
endPath = "$assetPath/index-min-v2.json",
diffPath = "$ASSET_PATH/diff-empty-min/23.json",
startPath = "$ASSET_PATH/index-empty-v2.json",
endPath = "$ASSET_PATH/index-min-v2.json",
)
@Test
fun testEmptyToMid() = testDiff(
diffPath = "$assetPath/diff-empty-mid/23.json",
startPath = "$assetPath/index-empty-v2.json",
endPath = "$assetPath/index-mid-v2.json",
diffPath = "$ASSET_PATH/diff-empty-mid/23.json",
startPath = "$ASSET_PATH/index-empty-v2.json",
endPath = "$ASSET_PATH/index-mid-v2.json",
)
@Test
fun testEmptyToMax() = testDiff(
diffPath = "$assetPath/diff-empty-max/23.json",
startPath = "$assetPath/index-empty-v2.json",
endPath = "$assetPath/index-max-v2.json",
diffPath = "$ASSET_PATH/diff-empty-max/23.json",
startPath = "$ASSET_PATH/index-empty-v2.json",
endPath = "$ASSET_PATH/index-max-v2.json",
)
@Test
fun testMinToMid() = testDiff(
diffPath = "$assetPath/diff-empty-mid/42.json",
startPath = "$assetPath/index-min-v2.json",
endPath = "$assetPath/index-mid-v2.json",
diffPath = "$ASSET_PATH/diff-empty-mid/42.json",
startPath = "$ASSET_PATH/index-min-v2.json",
endPath = "$ASSET_PATH/index-mid-v2.json",
)
@Test
fun testMinToMax() = testDiff(
diffPath = "$assetPath/diff-empty-max/42.json",
startPath = "$assetPath/index-min-v2.json",
endPath = "$assetPath/index-max-v2.json",
diffPath = "$ASSET_PATH/diff-empty-max/42.json",
startPath = "$ASSET_PATH/index-min-v2.json",
endPath = "$ASSET_PATH/index-max-v2.json",
)
@Test
fun testMidToMax() = testDiff(
diffPath = "$assetPath/diff-empty-max/1337.json",
startPath = "$assetPath/index-mid-v2.json",
endPath = "$assetPath/index-max-v2.json",
diffPath = "$ASSET_PATH/diff-empty-max/1337.json",
startPath = "$ASSET_PATH/index-mid-v2.json",
endPath = "$ASSET_PATH/index-max-v2.json",
)
@Test

View File

@@ -9,7 +9,7 @@ import org.fdroid.index.v2.IndexV2
public object IndexParser {
@Volatile
private var JSON: Json? = null
private var jsonInstance: Json? = null
/**
* Initializing [Json] is expensive, so using this method is preferable as it keeps returning
@@ -18,7 +18,7 @@ public object IndexParser {
public val json: Json
@JvmStatic
get() {
return JSON ?: synchronized(this) {
return jsonInstance ?: synchronized(this) {
Json {
ignoreUnknownKeys = true
}

View File

@@ -12,28 +12,28 @@ import org.fdroid.test.v1compat
import kotlin.test.Test
import kotlin.test.assertEquals
internal const val assetPath = "../sharedTest/src/main/assets"
internal const val ASSET_PATH = "../sharedTest/src/main/assets"
internal class IndexConverterTest {
@Test
fun testEmpty() {
testConversation("$assetPath/index-empty-v1.json", TestDataEmptyV2.index.v1compat())
testConversation("$ASSET_PATH/index-empty-v1.json", TestDataEmptyV2.index.v1compat())
}
@Test
fun testMin() {
testConversation("$assetPath/index-min-v1.json", TestDataMinV2.index.v1compat())
testConversation("$ASSET_PATH/index-min-v1.json", TestDataMinV2.index.v1compat())
}
@Test
fun testMid() {
testConversation("$assetPath/index-mid-v1.json", TestDataMidV2.indexCompat)
testConversation("$ASSET_PATH/index-mid-v1.json", TestDataMidV2.indexCompat)
}
@Test
fun testMax() {
testConversation("$assetPath/index-max-v1.json", TestDataMaxV2.indexCompat)
testConversation("$ASSET_PATH/index-max-v1.json", TestDataMaxV2.indexCompat)
}
private fun testConversation(file: String, expectedIndex: IndexV2) {

View File

@@ -3,7 +3,7 @@ package org.fdroid.index.v1
import com.goncalossilva.resources.Resource
import kotlinx.serialization.SerializationException
import org.fdroid.index.IndexParser.parseV1
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.fdroid.test.TestDataEmptyV1
import org.fdroid.test.TestDataMaxV1
import org.fdroid.test.TestDataMidV1
@@ -17,7 +17,7 @@ internal class IndexV1Test {
@Test
fun testIndexEmptyV1() {
val indexRes = Resource("$assetPath/index-empty-v1.json")
val indexRes = Resource("$ASSET_PATH/index-empty-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataEmptyV1.index, index)
@@ -25,7 +25,7 @@ internal class IndexV1Test {
@Test
fun testIndexMinV1() {
val indexRes = Resource("$assetPath/index-min-v1.json")
val indexRes = Resource("$ASSET_PATH/index-min-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataMinV1.index, index)
@@ -33,7 +33,7 @@ internal class IndexV1Test {
@Test
fun testIndexMidV1() {
val indexRes = Resource("$assetPath/index-mid-v1.json")
val indexRes = Resource("$ASSET_PATH/index-mid-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataMidV1.index, index)
@@ -41,7 +41,7 @@ internal class IndexV1Test {
@Test
fun testIndexMaxV1() {
val indexRes = Resource("$assetPath/index-max-v1.json")
val indexRes = Resource("$ASSET_PATH/index-max-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataMaxV1.index, index)
@@ -108,14 +108,14 @@ internal class IndexV1Test {
@Test
fun testGuardianProjectV1() {
val indexRes = Resource("$assetPath/guardianproject_index-v1.json")
val indexRes = Resource("$ASSET_PATH/guardianproject_index-v1.json")
val indexStr = indexRes.readText()
parseV1(indexStr)
}
@Test
fun testLocalizedV1() {
val indexRes = Resource("$assetPath/localized.json")
val indexRes = Resource("$ASSET_PATH/localized.json")
val indexStr = indexRes.readText()
parseV1(indexStr)
}

View File

@@ -3,7 +3,7 @@ package org.fdroid.index.v2
import com.goncalossilva.resources.Resource
import kotlinx.serialization.SerializationException
import org.fdroid.index.IndexParser
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.fdroid.test.TestDataEntry
import org.junit.Test
import kotlin.test.assertContains
@@ -14,22 +14,22 @@ internal class EntryTest {
@Test
fun testEmpty() {
testEntryEquality("$assetPath/entry-empty-v2.json", TestDataEntry.empty)
testEntryEquality("$ASSET_PATH/entry-empty-v2.json", TestDataEntry.empty)
}
@Test
fun testEmptyToMin() {
testEntryEquality("$assetPath/diff-empty-min/$DATA_FILE_NAME", TestDataEntry.emptyToMin)
testEntryEquality("$ASSET_PATH/diff-empty-min/$DATA_FILE_NAME", TestDataEntry.emptyToMin)
}
@Test
fun testEmptyToMid() {
testEntryEquality("$assetPath/diff-empty-mid/$DATA_FILE_NAME", TestDataEntry.emptyToMid)
testEntryEquality("$ASSET_PATH/diff-empty-mid/$DATA_FILE_NAME", TestDataEntry.emptyToMid)
}
@Test
fun testEmptyToMax() {
testEntryEquality("$assetPath/diff-empty-max/$DATA_FILE_NAME", TestDataEntry.emptyToMax)
testEntryEquality("$ASSET_PATH/diff-empty-max/$DATA_FILE_NAME", TestDataEntry.emptyToMax)
}
@Test

View File

@@ -5,7 +5,7 @@ import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import org.fdroid.index.IndexParser
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.File
@@ -17,22 +17,22 @@ import kotlin.test.fail
internal class IndexV2DiffStreamProcessorTest {
@Test
fun testEmptyToMin() = testDiff("$assetPath/diff-empty-min/23.json", 1)
fun testEmptyToMin() = testDiff("$ASSET_PATH/diff-empty-min/23.json", 1)
@Test
fun testEmptyToMid() = testDiff("$assetPath/diff-empty-mid/23.json", 2)
fun testEmptyToMid() = testDiff("$ASSET_PATH/diff-empty-mid/23.json", 2)
@Test
fun testEmptyToMax() = testDiff("$assetPath/diff-empty-max/23.json", 3)
fun testEmptyToMax() = testDiff("$ASSET_PATH/diff-empty-max/23.json", 3)
@Test
fun testMinToMid() = testDiff("$assetPath/diff-empty-mid/42.json", 2)
fun testMinToMid() = testDiff("$ASSET_PATH/diff-empty-mid/42.json", 2)
@Test
fun testMinToMax() = testDiff("$assetPath/diff-empty-max/42.json", 3)
fun testMinToMax() = testDiff("$ASSET_PATH/diff-empty-max/42.json", 3)
@Test
fun testMidToMax() = testDiff("$assetPath/diff-empty-max/1337.json", 2)
fun testMidToMax() = testDiff("$ASSET_PATH/diff-empty-max/1337.json", 2)
@Test
fun testRemovePackage() {

View File

@@ -3,7 +3,7 @@ package org.fdroid.index.v2
import com.goncalossilva.resources.Resource
import kotlinx.serialization.SerializationException
import org.fdroid.index.IndexParser.parseV2
import org.fdroid.index.assetPath
import org.fdroid.index.ASSET_PATH
import org.fdroid.test.TestDataEmptyV2
import org.fdroid.test.TestDataMaxV2
import org.fdroid.test.TestDataMidV2
@@ -17,27 +17,27 @@ internal class IndexV2Test {
@Test
fun testEmpty() {
testIndexEquality("$assetPath/index-empty-v2.json", TestDataEmptyV2.index)
testIndexEquality("$ASSET_PATH/index-empty-v2.json", TestDataEmptyV2.index)
}
@Test
fun testMin() {
testIndexEquality("$assetPath/index-min-v2.json", TestDataMinV2.index)
testIndexEquality("$ASSET_PATH/index-min-v2.json", TestDataMinV2.index)
}
@Test
fun testMinReordered() {
testIndexEquality("$assetPath/index-min-reordered-v2.json", TestDataMinV2.index)
testIndexEquality("$ASSET_PATH/index-min-reordered-v2.json", TestDataMinV2.index)
}
@Test
fun testMid() {
testIndexEquality("$assetPath/index-mid-v2.json", TestDataMidV2.index)
testIndexEquality("$ASSET_PATH/index-mid-v2.json", TestDataMidV2.index)
}
@Test
fun testMax() {
testIndexEquality("$assetPath/index-max-v2.json", TestDataMaxV2.index)
testIndexEquality("$ASSET_PATH/index-max-v2.json", TestDataMaxV2.index)
}
@Test

View File

@@ -1,7 +1,6 @@
plugins {
id 'kotlin-android'
id 'com.android.library'
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
}
kotlin {
@@ -15,7 +14,7 @@ java {
// not really an Android library, but index is not publishing for JVM at the moment
android {
namespace 'org.fdroid.test'
compileSdk 34
compileSdk libs.versions.compileSdk.get().toInteger()
defaultConfig {
minSdkVersion 21
}
@@ -25,9 +24,6 @@ dependencies {
implementation project(":libs:download")
implementation project(":libs:index")
implementation 'org.jetbrains.kotlin:kotlin-test'
// 1.4.1 because https://github.com/Kotlin/kotlinx.serialization/issues/2231
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation libs.kotlin.test
implementation libs.kotlinx.serialization.json
}
apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"

View File

@@ -42,8 +42,9 @@ object DiffUtils {
* Removes keys from a JSON object representing a [MetadataV2] which need special handling.
*/
fun JsonObject.cleanMetadata(): JsonObject {
val keysToFilter = listOf("icon", "featureGraphic", "promoGraphic", "tvBanner",
"screenshots")
val keysToFilter = listOf(
"icon", "featureGraphic", "promoGraphic", "tvBanner", "screenshots",
)
val newMap = filterKeys { it !in keysToFilter }
return JsonObject(newMap)
}

View File

@@ -33,9 +33,9 @@ object TestDataMinV1 {
description = "This is a repo with minimal data.",
)
const val packageName = "org.fdroid.min1"
const val PACKAGE_NAME = "org.fdroid.min1"
val app = AppV1(
packageName = packageName,
packageName = PACKAGE_NAME,
categories = emptyList(),
antiFeatures = emptyList(),
license = "",
@@ -43,15 +43,15 @@ object TestDataMinV1 {
val apps = listOf(app)
val version = PackageV1(
packageName = packageName,
apkName = "${packageName}_23.apk",
packageName = PACKAGE_NAME,
apkName = "${PACKAGE_NAME}_23.apk",
hash = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
hashType = "sha256",
size = 1337,
versionName = "0",
)
val versions = listOf(version)
val packages = mapOf(packageName to versions)
val packages = mapOf(PACKAGE_NAME to versions)
val index = IndexV1(
repo = repo,
@@ -74,11 +74,11 @@ object TestDataMidV1 {
mirrors = listOf("https://mid-v1.com"),
)
const val packageName1 = TestDataMinV1.packageName
const val packageName2 = "org.fdroid.fdroid"
const val PACKAGE_NAME_1 = TestDataMinV1.PACKAGE_NAME
const val PACKAGE_NAME_2 = "org.fdroid.fdroid"
val categories = listOf("Cat1", "Cat2", "Cat3")
val app1 = TestDataMinV1.app.copy(
packageName = packageName1,
packageName = PACKAGE_NAME_1,
categories = listOf(categories[0]),
antiFeatures = listOf("AntiFeature"),
summary = "App1 summary",
@@ -192,9 +192,9 @@ object TestDataMidV1 {
val version1_1 = TestDataMinV1.version.copy(
added = 2342,
apkName = "${packageName1}_23_2.apk",
apkName = "${PACKAGE_NAME_1}_23_2.apk",
size = 1338,
srcName = "${packageName1}_23_2.zip",
srcName = "${PACKAGE_NAME_1}_23_2.zip",
usesPermission = listOf(PermissionV1("perm")),
usesPermission23 = emptyList(),
versionCode = 1,
@@ -204,8 +204,8 @@ object TestDataMidV1 {
antiFeatures = listOf("anti-feature"),
)
val version1_2 = PackageV1(
packageName = packageName1,
apkName = "${packageName1}_42.apk",
packageName = PACKAGE_NAME_1,
apkName = "${PACKAGE_NAME_1}_42.apk",
hash = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
hashType = "sha256",
minSdkVersion = 21,
@@ -215,7 +215,7 @@ object TestDataMidV1 {
signer = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
features = listOf("new feature"),
size = 1337,
srcName = "${packageName1}_42.zip",
srcName = "${PACKAGE_NAME_1}_42.zip",
versionCode = 24,
versionName = "24",
)
@@ -396,8 +396,8 @@ object TestDataMidV1 {
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,
PACKAGE_NAME_1 to versions1,
PACKAGE_NAME_2 to versions2,
)
val index = IndexV1(
@@ -422,9 +422,9 @@ object TestDataMaxV1 {
mirrors = listOf("https://max-v1.com", "https://max-v1.org"),
)
const val packageName1 = TestDataMidV1.packageName1
const val packageName2 = TestDataMidV1.packageName2
const val packageName3 = "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moo" +
const val PACKAGE_NAME_1 = TestDataMidV1.PACKAGE_NAME_1
const val PACKAGE_NAME_2 = TestDataMidV1.PACKAGE_NAME_2
const val PACKAGE_NAME_3 = "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moo" +
"dahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5Ung" +
"ohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raeph" +
"oowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y"
@@ -468,7 +468,7 @@ object TestDataMaxV1 {
),
)
val app3 = AppV1(
packageName = packageName3,
packageName = PACKAGE_NAME_3,
categories = categories,
antiFeatures = listOf("AntiFeature", "NonFreeNet", "NotNice", "VeryBad", "Dont,Show,This"),
summary = "App3 summary",
@@ -510,8 +510,10 @@ object TestDataMaxV1 {
icon = "en ",
video = "en ",
phoneScreenshots = listOf("en phoneScreenshots", "en phoneScreenshots2"),
sevenInchScreenshots = listOf("en sevenInchScreenshots",
"en sevenInchScreenshots2"),
sevenInchScreenshots = listOf(
"en sevenInchScreenshots",
"en sevenInchScreenshots2",
),
tenInchScreenshots = listOf("en tenInchScreenshots", "en tenInchScreenshots2"),
wearScreenshots = listOf("en wearScreenshots", "en wearScreenshots2"),
tvScreenshots = listOf("en tvScreenshots", "en tvScreenshots2"),
@@ -605,9 +607,9 @@ object TestDataMaxV1 {
)
val versions3 = listOf(version3_1)
val packages = mapOf(
packageName1 to versions1,
packageName2 to versions2,
packageName3 to versions3,
PACKAGE_NAME_1 to versions1,
PACKAGE_NAME_2 to versions2,
PACKAGE_NAME_3 to versions3,
)
val index = IndexV1(

View File

@@ -127,11 +127,11 @@ object TestDataMinV2 {
description = mapOf(LOCALE to "This is a repo with minimal data."),
)
const val packageName = "org.fdroid.min1"
const val PACKAGE_NAME = "org.fdroid.min1"
val version = PackageVersionV2(
added = 0,
file = FileV1(
name = "/${packageName}_23.apk",
name = "/${PACKAGE_NAME}_23.apk",
sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
size = 1337,
),
@@ -150,7 +150,7 @@ object TestDataMinV2 {
version.file.sha256 to version,
),
)
val packages = mapOf(packageName to app)
val packages = mapOf(PACKAGE_NAME to app)
val index = IndexV2(
repo = repo,
@@ -206,17 +206,17 @@ object TestDataMidV2 {
)
val repoCompat = repo.v1compat()
const val packageName1 = TestDataMinV1.packageName
const val packageName2 = "org.fdroid.fdroid"
const val PACKAGE_NAME_1 = TestDataMinV1.PACKAGE_NAME
const val PACKAGE_NAME_2 = "org.fdroid.fdroid"
val version1_1 = PackageVersionV2(
added = 2342,
file = FileV1(
name = "/${packageName1}_23_2.apk",
name = "/${PACKAGE_NAME_1}_23_2.apk",
sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
size = 1338,
),
src = FileV2(
name = "/${packageName1}_23_2.zip",
name = "/${PACKAGE_NAME_1}_23_2.zip",
sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
size = 1338,
),
@@ -233,7 +233,7 @@ object TestDataMidV2 {
val version1_2 = PackageVersionV2(
added = 0,
file = FileV1(
name = "/${packageName1}_42.apk",
name = "/${PACKAGE_NAME_1}_42.apk",
sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
size = 1337,
),
@@ -253,7 +253,7 @@ object TestDataMidV2 {
),
),
src = FileV2(
name = "/${packageName1}_42.zip",
name = "/${PACKAGE_NAME_1}_42.zip",
sha256 = "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf",
size = 1338,
),
@@ -687,7 +687,7 @@ object TestDataMidV2 {
version2_5.file.sha256 to version2_5Compat,
),
)
val packages = mapOf(packageName1 to app1, packageName2 to app2)
val packages = mapOf(PACKAGE_NAME_1 to app1, PACKAGE_NAME_2 to app2)
val index = IndexV2(
repo = repo,
@@ -696,8 +696,8 @@ object TestDataMidV2 {
val indexCompat = index.copy(
repo = repoCompat,
packages = mapOf(
packageName1 to app1Compat,
packageName2 to app2Compat,
PACKAGE_NAME_1 to app1Compat,
PACKAGE_NAME_2 to app2Compat,
),
)
}
@@ -813,9 +813,9 @@ object TestDataMaxV2 {
),
)
const val packageName1 = TestDataMidV2.packageName1
const val packageName2 = TestDataMidV2.packageName2
const val packageName3 = "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moo" +
const val PACKAGE_NAME_1 = TestDataMidV2.PACKAGE_NAME_1
const val PACKAGE_NAME_2 = TestDataMidV2.PACKAGE_NAME_2
const val PACKAGE_NAME_3 = "Haoheiseeshai2que2Che0ooSa6aikeemoo2ap9Aequoh4ju5chooYuPhiev8moo" +
"dahlonu2oht5Eikahvushapeum5aefo6xig4aghahyaaNuezoo4eexee1Goo5Ung" +
"ohGha6quaeghe8uCh9iex9Oowa9aiyohzoo2ij5miifiegaeth8nie9jae6raeph" +
"oowishoor1Ien5vahGhahm7eidaiy2AeCaej9iexahyooshu2ic9tea1ool8tu4Y"
@@ -1262,16 +1262,16 @@ object TestDataMaxV2 {
val index = IndexV2(
repo = repo,
packages = mapOf(
TestDataMidV2.packageName1 to TestDataMidV2.app1,
TestDataMidV2.packageName2 to app2,
packageName3 to app3,
TestDataMidV2.PACKAGE_NAME_1 to TestDataMidV2.app1,
TestDataMidV2.PACKAGE_NAME_2 to app2,
PACKAGE_NAME_3 to app3,
),
)
val indexCompat = index.v1compat().copy(
packages = mapOf(
TestDataMidV2.packageName1 to TestDataMidV2.app1Compat,
TestDataMidV2.packageName2 to app2Compat,
packageName3 to app3Compat,
TestDataMidV2.PACKAGE_NAME_1 to TestDataMidV2.app1Compat,
TestDataMidV2.PACKAGE_NAME_2 to app2Compat,
PACKAGE_NAME_3 to app3Compat,
),
)
}

View File

@@ -1,3 +1,11 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':app'
include ':libs:sharedTest'
include ':libs:download'