Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987af545be | ||
|
|
297ba87f64 | ||
|
|
97f87eb3c8 | ||
|
|
c9100a927a | ||
|
|
bf52c20cb4 | ||
| 0d6e0bbcae | |||
| 0a60e93e66 | |||
| 480d5a885b | |||
| 67206b176e | |||
| 021af3619e | |||
|
|
d11c751f07 | ||
| f552c0bda1 | |||
| fcccc2126a | |||
| 801fa055f4 | |||
| bb00031a7b | |||
| fd97e6e514 | |||
| 08e351eecc | |||
| a730aa478d | |||
| 49def9086b | |||
| 70781e741c | |||
| 7fd7f0af8e | |||
| a7a8f9bce0 | |||
| 8076fbce74 | |||
| 9996ec3f1e | |||
| ab5a200ac8 | |||
| a7f730c0ff | |||
| 0928e06069 | |||
| 14bd5b3ea8 | |||
| a7a3e13b71 | |||
| a84b1190b4 | |||
| 4ab967faf0 | |||
| 39da43e271 | |||
| 8ff5bfc991 | |||
|
|
429081b94d | ||
| 5b1a938ac1 | |||
| f45acb7284 | |||
| 691c59950a | |||
|
|
c8ff87823a | ||
|
|
40391ded24 | ||
|
|
3412ab24b8 |
@@ -7,8 +7,8 @@ trim_trailing_whitespace = true
|
|||||||
|
|
||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
ij_kotlin_allow_trailing_comma=true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_name_count_to_use_star_import = 999
|
ij_kotlin_name_count_to_use_star_import = 999
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 999
|
ij_kotlin_name_count_to_use_star_import_for_members = 999
|
||||||
|
|
||||||
|
|||||||
59
.github/workflows/build_debug.yml
vendored
59
.github/workflows/build_debug.yml
vendored
@@ -1,5 +1,4 @@
|
|||||||
name: Build Debug APK
|
name: Build Debug APK
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -13,19 +12,15 @@ on:
|
|||||||
- '**.md'
|
- '**.md'
|
||||||
types: submitted
|
types: submitted
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}
|
group: ${{ github.workflow }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -33,10 +28,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/wrapper-validation-action@v3
|
|
||||||
|
|
||||||
- name: Set up Java 17
|
- name: Set up Java 17
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
@@ -45,30 +37,51 @@ jobs:
|
|||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v2
|
||||||
|
|
||||||
- name: Grant execution permission to Gradle Wrapper
|
- name: Grant execution permission to Gradle Wrapper
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build Debug APK
|
- name: Build Debug APK
|
||||||
run: ./gradlew assembleDebug
|
run: ./gradlew assembleDebug
|
||||||
|
|
||||||
- name: Sign Apk
|
- name: Display APK directory contents
|
||||||
continue-on-error: true
|
run: |
|
||||||
id: sign_apk
|
echo "Listing app/build/outputs/apk/debug contents:"
|
||||||
uses: r0adkll/sign-android-release@v1
|
ls -la app/build/outputs/apk/debug/
|
||||||
with:
|
|
||||||
releaseDir: app/build/outputs/apk/debug
|
|
||||||
signingKeyBase64: ${{ secrets.KEY_BASE64 }}
|
|
||||||
alias: ${{ secrets.KEY_ALIAS }}
|
|
||||||
keyStorePassword: ${{ secrets.KEYSTORE_PASS }}
|
|
||||||
keyPassword: ${{ secrets.KEYSTORE_PASS }}
|
|
||||||
|
|
||||||
- name: Remove file that aren't signed
|
# Using a manual signing approach instead of the failing action
|
||||||
|
- name: Sign APK manually
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
ls | grep 'signed\.apk$' && find . -type f -name '*.apk' ! -name '*-signed.apk' -delete
|
if [ -f app/build/outputs/apk/debug/app-debug.apk ]; then
|
||||||
|
echo "Found APK file to sign"
|
||||||
|
|
||||||
|
# Create keystore from base64
|
||||||
|
echo "${{ secrets.KEY_BASE64 }}" | base64 -d > keystore.jks
|
||||||
|
|
||||||
|
# Sign APK using apksigner
|
||||||
|
$ANDROID_HOME/build-tools/*/apksigner sign --ks keystore.jks \
|
||||||
|
--ks-key-alias "${{ secrets.KEY_ALIAS }}" \
|
||||||
|
--ks-pass pass:"${{ secrets.KEYSTORE_PASS }}" \
|
||||||
|
--key-pass pass:"${{ secrets.KEYSTORE_PASS }}" \
|
||||||
|
--out app/build/outputs/apk/debug/app-debug-signed.apk \
|
||||||
|
app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
|
||||||
|
echo "APK signed successfully"
|
||||||
|
else
|
||||||
|
echo "No APK file found to sign"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Display signed APK files
|
||||||
|
run: |
|
||||||
|
echo "Listing signed APK files:"
|
||||||
|
find app/build/outputs/apk/debug/ -name "*-signed.apk" || echo "No signed APK found"
|
||||||
|
|
||||||
|
# Using v3 of upload-artifact which is more widely supported
|
||||||
- name: Upload the APK
|
- name: Upload the APK
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: debug
|
name: debug
|
||||||
path: app/build/outputs/apk/debug/app-debug*.apk
|
path: app/build/outputs/apk/debug/*.apk
|
||||||
94
.github/workflows/release_build.yml
vendored
94
.github/workflows/release_build.yml
vendored
@@ -1,94 +0,0 @@
|
|||||||
name: Build Release APK
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: "release-build"
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/wrapper-validation-action@v3
|
|
||||||
|
|
||||||
- name: Set up Java 17
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: 'adopt'
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Grant execution permission to Gradle Wrapper
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
- name: Build Release APK
|
|
||||||
run: ./gradlew assembleRelease
|
|
||||||
|
|
||||||
- name: Checks
|
|
||||||
run: find . -type f -name "*.apk"
|
|
||||||
|
|
||||||
- uses: r0adkll/sign-android-release@v1
|
|
||||||
name: Signing APK
|
|
||||||
id: sign_app
|
|
||||||
with:
|
|
||||||
releaseDirectory: app/build/outputs/apk/release
|
|
||||||
signingKeyBase64: ${{ secrets.KEY_BASE64 }}
|
|
||||||
alias: ${{ secrets.KEY_ALIAS }}
|
|
||||||
keyStorePassword: ${{ secrets.KEYSTORE_PASS }}
|
|
||||||
keyPassword: ${{ secrets.KEYSTORE_PASS }}
|
|
||||||
env:
|
|
||||||
BUILD_TOOLS_VERSION: "35.0.0"
|
|
||||||
|
|
||||||
- name: Extract Version Code
|
|
||||||
id: extract_version
|
|
||||||
run: |
|
|
||||||
VERSION_CODE=$(grep -oP '(?<=versionCode=)\d+' app/build.gradle.kts) # Adjust path to your build.gradle
|
|
||||||
echo "::set-output name=version_code::$VERSION_CODE"
|
|
||||||
echo "Version Code: $VERSION_CODE"
|
|
||||||
|
|
||||||
- name: Read Changelog
|
|
||||||
id: read_changelog
|
|
||||||
run: |
|
|
||||||
CHANGELOG_PATH="metadata/en-US/changelogs/${{ steps.extract_version.outputs.version_code }}.txt"
|
|
||||||
if [[ -f "$CHANGELOG_PATH" ]]; then
|
|
||||||
CHANGELOG=$(cat "$CHANGELOG_PATH")
|
|
||||||
echo "::set-output name=changelog::$CHANGELOG"
|
|
||||||
else
|
|
||||||
echo "::set-output name=changelog::No changelog found for this version."
|
|
||||||
echo "No changelog found at: $CHANGELOG_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: softprops/action-gh-release@v2
|
|
||||||
name: Create Release
|
|
||||||
id: publish_release
|
|
||||||
with:
|
|
||||||
body: ${{ steps.read_changelog.outputs.changelog }}
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
name: Release ${{ github.ref }}
|
|
||||||
files: ${{steps.sign_app.outputs.signedReleaseFile}}
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: Signed APK
|
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
|
||||||
39
README.md
39
README.md
@@ -1,33 +1,21 @@
|
|||||||
<div align="center">
|
## Download
|
||||||
|
|
||||||
<img width="" src="metadata/en-US/images/featureGraphic.png" alt="Droid-ify" align="center">
|
- Click [here](https://git.felo.gg/FeloStore/FeloStore/releases/download/v0.0.1/felostore.apk) to download the latest version
|
||||||
|
|
||||||
[](https://github.com/Iamlooker/Droid-ify/stargazers)
|
|
||||||
[](https://github.com/Iamlooker/Droid-ify/blob/master/COPYING)
|
|
||||||
[](https://github.com/Iamlooker/Droid-ify/releases/)
|
|
||||||
[](https://github.com/Iamlooker/Droid-ify/releases/latest)
|
|
||||||
[](https://f-droid.org/packages/com.looker.droidify)
|
|
||||||
</div>
|
|
||||||
<div align="left">
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Clean Material 3 design
|
* Material & Clean design
|
||||||
* Fast repository syncing
|
* Fast repository syncing
|
||||||
* Smooth user experience
|
* Smooth user experience
|
||||||
* Feature-rich
|
* Feature-rich
|
||||||
|
|
||||||
## Screenshots
|
## Building and Installing
|
||||||
|
|
||||||
<img src="metadata/en-US/images/phoneScreenshots/1.png" width="25%" /><img src="metadata/en-US/images/phoneScreenshots/2.png" width="25%" /><img src="metadata/en-US/images/phoneScreenshots/3.png" width="25%" /><img src="metadata/en-US/images/phoneScreenshots/4.png" width="25%" />
|
|
||||||
|
|
||||||
## Building and installing
|
|
||||||
|
|
||||||
1. **Install Android Studio**:
|
1. **Install Android Studio**:
|
||||||
- Download and install [Android Studio](https://developer.android.com/studio) on your computer
|
- Download and install [Android Studio](https://developer.android.com/studio) on your computer
|
||||||
if you haven't already.
|
if you haven't already.
|
||||||
|
|
||||||
2. **Clone the repository**:
|
2. **Clone the Repository**:
|
||||||
- Open Android Studio and select "Project from Version Control."
|
- Open Android Studio and select "Project from Version Control."
|
||||||
- Paste the link to this repository to clone it to your local machine.
|
- Paste the link to this repository to clone it to your local machine.
|
||||||
|
|
||||||
@@ -36,26 +24,17 @@
|
|||||||
- Select "Create New Keystore" and enter the required information, including a password.
|
- Select "Create New Keystore" and enter the required information, including a password.
|
||||||
- Wait for the build process to finish.
|
- Wait for the build process to finish.
|
||||||
|
|
||||||
## TODO
|
## Contribution
|
||||||
|
|
||||||
- [ ] Add support for `index-v2`
|
|
||||||
- [ ] Add detekt code analysis
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
- Pick any issue you would like to resolve
|
- Pick any issue you would like to resolve
|
||||||
- Fork the project
|
- Fork the project
|
||||||
- Open a pull request
|
- Open a Pull Request
|
||||||
- Your pull request will undergo review
|
- Your PR will undergo review
|
||||||
|
|
||||||
## Translations
|
|
||||||
|
|
||||||
[](https://hosted.weblate.org/engage/droidify/?utm_source=widget)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
Droid-ify
|
FeloStore
|
||||||
|
|
||||||
Copyright (C) 2023 LooKeR
|
Copyright (C) 2023 LooKeR
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ It is only natural that users will grow impatient for an update, thats why I wil
|
|||||||
|
|
||||||
## What now?
|
## What now?
|
||||||
- Next release will be on **19-Aug**, and will contain many bug fixes and stability improvements.
|
- Next release will be on **19-Aug**, and will contain many bug fixes and stability improvements.
|
||||||
- All the [progress](https://github.com/Droid-ify/client/pull/309) made in past months is still here and will help in future.
|
- All the [progress](https://github.com/FeloStore/client/pull/309) made in past months is still here and will help in future.
|
||||||
- We will be missing on the new index format introduced by fdroid for some future releases.
|
- We will be missing on the new index format introduced by fdroid for some future releases.
|
||||||
|
|||||||
8
UPDATING.md
Normal file
8
UPDATING.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
1. Delete everything in the "FeloStore/Releases" Repo
|
||||||
|
2. Put new version of FeloStore from the GitHub's "Release"-Tab in the "FeloStore/Releases" Repo
|
||||||
|
|
||||||
|
|
||||||
|
3. Run these commands in the FeloStore/FeloStore Repo:
|
||||||
|
- git fetch upstream
|
||||||
|
- git checkout main
|
||||||
|
- git merge upstream/main
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.ktlint)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
alias(libs.plugins.hilt)
|
alias(libs.plugins.hilt)
|
||||||
alias(libs.plugins.kotlin.serialization)
|
alias(libs.plugins.kotlin.serialization)
|
||||||
@@ -12,59 +12,68 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
val latestVersionName = "0.6.6"
|
val latestVersionName = "0.0.2"
|
||||||
namespace = "com.looker.droidify"
|
namespace = "de.felitendo.felostore"
|
||||||
buildToolsVersion = "35.0.0"
|
buildToolsVersion = "35.0.0"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
applicationId = "com.looker.droidify"
|
applicationId = "de.felitendo.felostore"
|
||||||
versionCode = 660
|
versionCode = 650
|
||||||
versionName = latestVersionName
|
versionName = latestVersionName
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = false
|
||||||
testInstrumentationRunner = "com.looker.droidify.TestRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions.isCoreLibraryDesugaringEnabled = true
|
compileOptions {
|
||||||
kotlinOptions.freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn", "-Xcontext-parameters")
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
androidResources.generateLocaleConfig = true
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlinOptions {
|
||||||
jvmToolchain(17)
|
jvmTarget = "17"
|
||||||
compilerOptions {
|
freeCompilerArgs = listOf(
|
||||||
languageVersion.set(KotlinVersion.KOTLIN_2_2)
|
"-opt-in=kotlin.RequiresOptIn",
|
||||||
apiVersion.set(KotlinVersion.KOTLIN_2_2)
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
jvmTarget.set(JvmTarget.JVM_17)
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-Xcontext-receivers"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidResources {
|
||||||
|
generateLocaleConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets.forEach { source ->
|
||||||
|
val javaDir = source.java.srcDirs.find { it.name == "java" }
|
||||||
|
source.java {
|
||||||
|
srcDir(File(javaDir?.parentFile, "kotlin"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ksp {
|
|
||||||
arg("room.schemaLocation", "$projectDir/schemas")
|
|
||||||
arg("room.generateKotlin", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
resValue("string", "application_name", "Droid-ify-Debug")
|
resValue("string", "application_name", "Felo Store")
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isShrinkResources = true
|
isShrinkResources = true
|
||||||
resValue("string", "application_name", "Droid-ify")
|
resValue("string", "application_name", "Felo Store")
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard.pro",
|
"proguard.pro"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
create("alpha") {
|
create("alpha") {
|
||||||
initWith(getByName("debug"))
|
initWith(getByName("debug"))
|
||||||
applicationIdSuffix = ".alpha"
|
applicationIdSuffix = ".alpha"
|
||||||
resValue("string", "application_name", "Droid-ify Alpha")
|
resValue("string", "application_name", "Felo Store")
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard.pro",
|
"proguard.pro"
|
||||||
)
|
)
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
@@ -73,7 +82,7 @@ android {
|
|||||||
buildConfigField(
|
buildConfigField(
|
||||||
type = "String",
|
type = "String",
|
||||||
name = "VERSION_NAME",
|
name = "VERSION_NAME",
|
||||||
value = "\"v$latestVersionName\"",
|
value = "\"v$latestVersionName\""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,6 +111,18 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ktlint {
|
||||||
|
android.set(true)
|
||||||
|
ignoreFailures.set(true)
|
||||||
|
debug.set(true)
|
||||||
|
reporters {
|
||||||
|
reporter(ReporterType.HTML)
|
||||||
|
}
|
||||||
|
filter {
|
||||||
|
exclude("**/generated/**")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring(libs.desugaring)
|
coreLibraryDesugaring(libs.desugaring)
|
||||||
|
|
||||||
@@ -123,17 +144,19 @@ dependencies {
|
|||||||
implementation(libs.kotlin.stdlib)
|
implementation(libs.kotlin.stdlib)
|
||||||
implementation(libs.datetime)
|
implementation(libs.datetime)
|
||||||
|
|
||||||
implementation(libs.bundles.coroutines)
|
implementation(libs.coroutines.core)
|
||||||
|
implementation(libs.coroutines.android)
|
||||||
|
implementation(libs.coroutines.guava)
|
||||||
|
|
||||||
implementation(libs.libsu.core)
|
implementation(libs.libsu.core)
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.shizuku.api)
|
||||||
|
api(libs.shizuku.provider)
|
||||||
|
|
||||||
implementation(libs.jackson.core)
|
implementation(libs.jackson.core)
|
||||||
implementation(libs.serialization)
|
implementation(libs.serialization)
|
||||||
|
|
||||||
implementation(libs.bundles.ktor)
|
implementation(libs.ktor.core)
|
||||||
implementation(libs.bundles.room)
|
implementation(libs.ktor.okhttp)
|
||||||
ksp(libs.room.compiler)
|
|
||||||
|
|
||||||
implementation(libs.work.ktx)
|
implementation(libs.work.ktx)
|
||||||
|
|
||||||
@@ -146,16 +169,13 @@ dependencies {
|
|||||||
testImplementation(platform(libs.junit.bom))
|
testImplementation(platform(libs.junit.bom))
|
||||||
testImplementation(libs.bundles.test.unit)
|
testImplementation(libs.bundles.test.unit)
|
||||||
testRuntimeOnly(libs.junit.platform)
|
testRuntimeOnly(libs.junit.platform)
|
||||||
androidTestImplementation(libs.hilt.test)
|
androidTestImplementation(platform(libs.junit.bom))
|
||||||
androidTestImplementation(libs.room.test)
|
|
||||||
androidTestImplementation(libs.bundles.test.android)
|
androidTestImplementation(libs.bundles.test.android)
|
||||||
kspAndroidTest(libs.hilt.compiler)
|
|
||||||
|
|
||||||
// debugImplementation(libs.leakcanary)
|
// debugImplementation(libs.leakcanary)
|
||||||
}
|
}
|
||||||
|
|
||||||
// using a task as a preBuild dependency instead of a function that takes some time insures that it runs
|
// using a task as a preBuild dependency instead of a function that takes some time insures that it runs
|
||||||
// in /res are (almost) all languages that have a translated string is saved. this is safer and saves some time
|
|
||||||
task("detectAndroidLocals") {
|
task("detectAndroidLocals") {
|
||||||
val langsList: MutableSet<String> = HashSet()
|
val langsList: MutableSet<String> = HashSet()
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string-array name="default_repos">
|
|
||||||
<!-- SHIFT -->
|
|
||||||
<!-- name -->
|
|
||||||
<item>SHIFT</item>
|
|
||||||
<!-- address -->
|
|
||||||
<item>https://fdroid.shiftphones.com/fdroid/repo</item>
|
|
||||||
<!-- description -->
|
|
||||||
<item>Provides updates and optional apps for your SHIFT device.</item>
|
|
||||||
<!-- version -->
|
|
||||||
<item>20002</item>
|
|
||||||
<!-- enabled -->
|
|
||||||
<item>1</item>
|
|
||||||
<!-- priority (disabled for additional_repos.xml) -->
|
|
||||||
<!--<item>1</item>-->
|
|
||||||
<!-- push requests -->
|
|
||||||
<item>ignore</item>
|
|
||||||
<!-- pubkey -->
|
|
||||||
<item>308205ea308203d2a003020102020900e74081628660407c300d06092a864886f70d01010b05003081a13125302306092a864886f70d01090116166664726f696440736869667470686f6e65732e636f6d311f301d060355040313166664726f69642e736869667470686f6e65732e636f6d31143012060355040b130b534849465450484f4e4553310e300c060355040a13055348494654311330110603550407130a46616c6b656e62657267310f300d0603550408130648657373656e310b30090603550406130244453020170d3235303630353039353133345a180f32303532313032313039353133345a3081a13125302306092a864886f70d01090116166664726f696440736869667470686f6e65732e636f6d311f301d060355040313166664726f69642e736869667470686f6e65732e636f6d31143012060355040b130b534849465450484f4e4553310e300c060355040a13055348494654311330110603550407130a46616c6b656e62657267310f300d0603550408130648657373656e310b300906035504061302444530820222300d06092a864886f70d01010105000382020f003082020a0282020100b5042d416fde7b9eb1b4f399030da3bb7f903e29823aca5d193ef4e2bea848375e2ee1e1a599aa7d157292ba09759938d40b38c5fccaa12f6877a8335c3a1646788a4282dddf197f8e264bf1241efb93f12dfdaed175c4df2a4f68701db5518a6a76c7f51815154d6909598a8ba8af731197ee03d79fc3b5bbbb5fb0d0435e1a33caa7a8c9a4cd9d69c625663741b889efb505d634c8b11765bc703831b8db1cec78c7c2ccec83e211565f13a253a2b320a88621c68978cf7530b01a61796cfd56109ffa09525c1dc20dbabc9b77a0b3e547af052c31a7c137500ee82269cef267fd2c87f3c58036f69561eac39e9ba03ab7e0627880212285f165bbfa6ed12997921891387317c9185695e7ee4694955ff76ef17f9ad8dadd60d5fd0f9b43ce6cb6c8484dfea300fdec8c4dc59d07697694590e28f0f4f01e2dd72afdcb1ec64e95ee2712ba0cf0120807bcd307090af5a7423ace98c990d9b3ae132fbc61414334af2d9ceb81aa460a79529fcab2fd5565c4b7027e6697164d07b800a23b202fc23140055fb36090f78dc24a87483e16dd40e7270a47f020035e352d67fc5ddfcfee82de243f5688e268d1450bca1cb6dcbfc0c5f85e675293d8907b96951cf913fef33177248c3442c59e0dfb75803dd55ef750c0ff80997fde5bf36bf9c4cc6d0dc562e5ed91ccd9139f7885cb7984d61ee6e71b5616fb5e8bf89162c98f0203010001a321301f301d0603551d0e041604142e046a0536dcc228ae2c82770be90c751d4f6fc7300d06092a864886f70d01010b050003820201006cd87b00983dad8f298f9bb880ab982bf25cc6c12002cfa6494b76d08fea86a19f035d42b5f3551896531c67df5618e3153e822c218f220e875cac9e89fab080f0a1410a776f745417110275766c494bc598323481595d75dd019e15dcef8a94814155fc76531feaada8f53d364b75f8bb59ad3493b2bb483d037fcbfa136030ee842a6f3babe851224cd5f8e9ada88ad0eb2f894989310f4bc5751bdf39becccb2a261b76c46ebd4a6df533c990b88e65d21d1ac566d8f801b93a97b9b316f6c3bc40442018429290e06056dbe07c49fca4ef614212421040971d65182acc2c49c0cb8fdd533590e61b7889c39464478a4936f914c276114ae4478bcfad8d0ec67b8a05c7ee83e442ba66775a2376c9939ff54e56e6485abefd31ec89c5d3dcf66df26f590c4a369a04fd4400518eae5e7312d5960da573d18d26dc8717e2913b6f2ccd4dc07958b6d40856ba0720727524127e91656b42cd969161c8e07f66896c2f5bf40268b3c6a84a600492b607f09f5bee013532ead0c1d4773aaab361141c0b44ac31fd40368c92828597758575a27d3b079ce98a41b5837f8007d8890354f5549b150a5bf0bce792bda1981daf0bcf90f2754d74100d85e1110fa6749bf43ba36afe33f828a4341ad5c161878da07a6c87479bb183ebbfa83bbe646da17a26b9947ceb2ee3c94114c18b1d76b7236f585031a0d21dd6359493a8d043</item>
|
|
||||||
|
|
||||||
<!-- microG -->
|
|
||||||
<!-- name -->
|
|
||||||
<item>microG F-Droid repo</item>
|
|
||||||
<!-- address -->
|
|
||||||
<item>https://microg.org/fdroid/repo</item>
|
|
||||||
<!-- description -->
|
|
||||||
<item>This is a repository of microG apps to be used with F-Droid. Applications in this repository are signed official binaries built by the microG Team from the corresponding source code.</item>
|
|
||||||
<!-- version -->
|
|
||||||
<item>20002</item>
|
|
||||||
<!-- enabled -->
|
|
||||||
<item>1</item>
|
|
||||||
<!-- priority (disabled for additional_repos.xml) -->
|
|
||||||
<!--<item>1</item>-->
|
|
||||||
<!-- push requests -->
|
|
||||||
<item>ignore</item>
|
|
||||||
<!-- pubkey -->
|
|
||||||
<item>308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37</item>
|
|
||||||
|
|
||||||
<!-- IzzyOnDroid -->
|
|
||||||
<!-- name -->
|
|
||||||
<item>IzzyOnDroid F-Droid Repo</item>
|
|
||||||
<!-- address -->
|
|
||||||
<item>https://apt.izzysoft.de/fdroid/repo</item>
|
|
||||||
<!-- description -->
|
|
||||||
<item>This is a repository of apps to be used with F-Droid. Applications in this repository are official binaries built by the original application developers, taken from their resp. repositories (mostly Github, GitLab, Codeberg). Updates for the apps are usually fetched daily, and you can expect daily index updates.</item>
|
|
||||||
<!-- version -->
|
|
||||||
<item>20002</item>
|
|
||||||
<!-- enabled -->
|
|
||||||
<item>1</item>
|
|
||||||
<!-- priority (disabled for additional_repos.xml) -->
|
|
||||||
<!--<item>1</item>-->
|
|
||||||
<!-- push requests -->
|
|
||||||
<item>ignore</item>
|
|
||||||
<!-- pubkey -->
|
|
||||||
<item>308204e1308202c9a003020102020454c60934300d06092a864886f70d01010b050030213110300e060355040b1307462d44726f6964310d300b060355040313046e65626f301e170d3136303331303230313634325a170d3433303732373230313634325a30213110300e060355040b1307462d44726f6964310d300b060355040313046e65626f30820222300d06092a864886f70d01010105000382020f003082020a0282020100ac59258ca2e9c216af14d58cb53adb13658480aed5ebc1f59bfc474f0f67c0efe9d58304d0cbda2897bd3283e7afe15512f32743ee243f4b9bba5a017806bc5c3441c905df37d00d3cf77b012af33ee4033b7e8d686277043bcb28241a3fe9f6ebfd72f305a928e300edf554ffaa139d85b5c9282aa8f1a82ff74caea2c13006dbeae8aac9ff44fa4c9122808b90c304db8b9e6ddecdbfbf5ce4ed0115cf1ba2bc6a4d6211765553df9b650db69155448aec4b0aaf59d19712aca3010a0d96eb02ed84e90c16162272af32fe909a5acde37d78fba500994f50c1ec5afa528945a7567567560a9fbafbabd68190c5c13f9a53f39a72734bd8de43c06b21a5cecf2747e6a1879352c49ee29fa092c26ca495baac69eddb614941e27b6a27fb3fb74cbdfe5822bfc266130c1f723a7ab91ed3d6c5261d31fc80ab82b7caa2727120522e65863af436a438c05039e1e099faae4d6170baa10fc9bb7bf101e2b4c9769e693eb7e4e3eebd47bfbfe0069c24a8b1ef72d8fe6549202490cff7b0f36c458b8192fe58f984839290d69639abb15fe1ef2925eb491627f2eefbd13225b925a7bbfc0fb4d95a3fb43599c172037e599639b4f86c4eabc173013776a854e146dfacf424cbae4254f9806ecd79d092f5e67a2f00c98ad64c0bfbeaff117fe4c62685e2e75e2ef507325d05f866510c20006a6c01e8e25d75bd42a0d5397b73eb0203010001a321301f301d0603551d0e0416041417f4fd41b0aa3f4fa981423a123f6f6016e3ce80300d06092a864886f70d01010b050003820201008d5d93cbb48fde9df566d75c54a8da2f29e9ae1bac2ed2436a0f165730244ac9e471b473674bc68717c34e30c29ce5ffa027fa12a7eb2f45b036db0cca79238262ba84f6ec8ffddcfe2b398c0a6aa33d117f83996b3bece96b1ea6f8066c395e5021c2b5fe1638c7ac146cda6ef2e4a836bd9c968ed76c51cc0b09caa4b1a79d5d10b3829804db992a70feb9a76535bc04631193abee9c9d7ebfb07ad464542f65744e76d92c5aeb3beb96dbb0b3d746845cbfa2b12c6da31863ea4a0d664dc5974d5b808c1be52a5e595ed181d86feeff4dc82bc8ee3c11ff807a811322931e804df1d90b5b813dd9ce81f3d8dd7d1bb2994901fe1c1004673f53c7b60cdbc2f914ce0718fbfc8e89b443091f71ecb9f169d558c3818bb1db714a47025154eb974600ca54e29933a87a4080910eee05dcc34de7048fa95b1128d8910b18b5957f2e745de00decd2434af455b24aa3e53de889e37919212a6adb3f4088baec6cc9f3e21b812593605fba0394355bd994f21ceaba861aae29244f5113d4291fdddedbef091e63885ebf318c6e12d338fa9555783643a19181c2cc935307fcee5e6dabf8dd6e19a92b29dbc529d3ef170916fb7b2d9dbf95a358ac7c0204b6e6a416b59441c49c41d6f78b1de63eb8b10c516a5952a20eb0c595cfa21530350c5adde74d815918deb870a9e7750fcb4dc50538fd591006434cbbb001cc2ae1fe11</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,34 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import androidx.test.filters.SmallTest
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import com.looker.droidify.index.OemRepositoryParser
|
|
||||||
import com.looker.droidify.sync.common.assets
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertContentEquals
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@SmallTest
|
|
||||||
class OemRepositoryParserTest {
|
|
||||||
|
|
||||||
private lateinit var context: Context
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun parseFile() {
|
|
||||||
val stream = assets("additional_repos.xml")
|
|
||||||
val list = OemRepositoryParser.parse(stream)
|
|
||||||
assertEquals(3, list.size)
|
|
||||||
val listOfNames = list.map { it.name }
|
|
||||||
assertContentEquals(listOfNames, listOf("SHIFT", "microG F-Droid repo", "IzzyOnDroid F-Droid Repo"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.looker.droidify.data.local.dao.AppDao
|
|
||||||
import com.looker.droidify.data.local.dao.IndexDao
|
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
|
||||||
import com.looker.droidify.domain.model.Repo
|
|
||||||
import com.looker.droidify.domain.model.VersionInfo
|
|
||||||
import com.looker.droidify.model.Repository
|
|
||||||
import com.looker.droidify.sync.FakeDownloader
|
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
|
||||||
import com.looker.droidify.sync.common.downloadIndex
|
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@HiltAndroidTest
|
|
||||||
class RoomTesting {
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var indexDao: IndexDao
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var appDao: AppDao
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
@ApplicationContext
|
|
||||||
lateinit var context: Context
|
|
||||||
|
|
||||||
private val defaults = Repository.defaultRepositories
|
|
||||||
private val izzyLegacy = defaults[4]
|
|
||||||
private val fdroidLegacy = defaults[0]
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun before() = runTest {
|
|
||||||
hiltRule.inject()
|
|
||||||
launch {
|
|
||||||
val izzy = izzyLegacy.toRepo(1)
|
|
||||||
val izzyFile = FakeDownloader.downloadIndex(context, izzy, "i2", "index-v2.json")
|
|
||||||
val izzyIndex =
|
|
||||||
JsonParser.decodeFromString<IndexV2>(izzyFile.readBytes().decodeToString())
|
|
||||||
indexDao.insertIndex(
|
|
||||||
fingerprint = izzy.fingerprint!!,
|
|
||||||
index = izzyIndex,
|
|
||||||
expectedRepoId = izzy.id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// launch {
|
|
||||||
// val fdroid = fdroidLegacy.toRepo(2)
|
|
||||||
// val fdroidFile =
|
|
||||||
// FakeDownloader.downloadIndex(context, fdroid, "f2", "fdroid-index-v2.json")
|
|
||||||
// val fdroidIndex =
|
|
||||||
// JsonParser.decodeFromString<IndexV2>(fdroidFile.readBytes().decodeToString())
|
|
||||||
// indexDao.insertIndex(
|
|
||||||
// fingerprint = fdroid.fingerprint!!,
|
|
||||||
// index = fdroidIndex,
|
|
||||||
// expectedRepoId = fdroid.id,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun sortOrderTest() = runTest {
|
|
||||||
val lastUpdatedQuery = appDao.query(sortOrder = SortOrder.UPDATED)
|
|
||||||
var previousUpdated = Long.MAX_VALUE
|
|
||||||
lastUpdatedQuery.forEach {
|
|
||||||
println("Previous: $previousUpdated, Current: ${it.lastUpdated}")
|
|
||||||
assertTrue(it.lastUpdated <= previousUpdated)
|
|
||||||
previousUpdated = it.lastUpdated
|
|
||||||
}
|
|
||||||
|
|
||||||
val addedQuery = appDao.query(sortOrder = SortOrder.ADDED)
|
|
||||||
var previousAdded = Long.MAX_VALUE
|
|
||||||
addedQuery.forEach {
|
|
||||||
println("Previous: $previousAdded, Current: ${it.added}")
|
|
||||||
assertTrue(it.added <= previousAdded)
|
|
||||||
previousAdded = it.added
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun categoryTest() = runTest {
|
|
||||||
val categoryQuery = appDao.query(
|
|
||||||
sortOrder = SortOrder.UPDATED,
|
|
||||||
categoriesToInclude = listOf("Games", "Food"),
|
|
||||||
)
|
|
||||||
val nonCategoryQuery = appDao.query(
|
|
||||||
sortOrder = SortOrder.UPDATED,
|
|
||||||
categoriesToExclude = listOf("Games", "Food"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Repository.toRepo(id: Int) = Repo(
|
|
||||||
id = id,
|
|
||||||
enabled = enabled,
|
|
||||||
address = address,
|
|
||||||
name = name,
|
|
||||||
description = description,
|
|
||||||
fingerprint = Fingerprint(fingerprint),
|
|
||||||
authentication = null,
|
|
||||||
versionInfo = VersionInfo(timestamp, entityTag),
|
|
||||||
mirrors = emptyList(),
|
|
||||||
)
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.test.runner.AndroidJUnitRunner
|
|
||||||
import dagger.hilt.android.testing.HiltTestApplication
|
|
||||||
|
|
||||||
class TestRunner : AndroidJUnitRunner() {
|
|
||||||
override fun newApplication(
|
|
||||||
cl: ClassLoader,
|
|
||||||
appName: String,
|
|
||||||
context: Context,
|
|
||||||
): Application {
|
|
||||||
return super.newApplication(cl, HiltTestApplication::class.java.getName(), context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
package com.looker.droidify.index
|
package de.felitendo.felostore.index
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.looker.droidify.database.Database
|
import de.felitendo.felostore.model.Repository
|
||||||
import com.looker.droidify.index.RepositoryUpdater.IndexType
|
|
||||||
import com.looker.droidify.model.Repository
|
|
||||||
import com.looker.droidify.sync.FakeDownloader
|
|
||||||
import com.looker.droidify.sync.common.assets
|
|
||||||
import com.looker.droidify.sync.common.benchmark
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.math.sqrt
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@@ -27,9 +21,7 @@ class RepositoryUpdaterTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
context = InstrumentationRegistry.getInstrumentation().context
|
||||||
Database.init(context)
|
|
||||||
RepositoryUpdater.init(CoroutineScope(Dispatchers.Default), FakeDownloader)
|
|
||||||
repository = Repository(
|
repository = Repository(
|
||||||
id = 15,
|
id = 15,
|
||||||
address = "https://apt.izzysoft.de/fdroid/repo",
|
address = "https://apt.izzysoft.de/fdroid/repo",
|
||||||
@@ -49,14 +41,13 @@ class RepositoryUpdaterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun processFile() {
|
fun processFile() {
|
||||||
val output = benchmark(1) {
|
testRepetition(1) {
|
||||||
val createFile = File.createTempFile("index", "entry")
|
val createFile = File.createTempFile("index", "entry")
|
||||||
val mergerFile = File.createTempFile("index", "merger")
|
val mergerFile = File.createTempFile("index", "merger")
|
||||||
val jarStream = context.resources.assets.open("index-v1.jar")
|
val jarStream = context.resources.assets.open("index-v1.jar")
|
||||||
jarStream.copyTo(createFile.outputStream())
|
jarStream.copyTo(createFile.outputStream())
|
||||||
process(createFile, mergerFile)
|
process(createFile, mergerFile)
|
||||||
}
|
}
|
||||||
println(output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun process(file: File, merger: File) = measureTimeMillis {
|
private fun process(file: File, merger: File) = measureTimeMillis {
|
||||||
@@ -74,4 +65,28 @@ class RepositoryUpdaterTest {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun testRepetition(repetition: Int, block: () -> Long) {
|
||||||
|
val times = (1..repetition).map {
|
||||||
|
System.gc()
|
||||||
|
System.runFinalization()
|
||||||
|
block().toDouble()
|
||||||
|
}
|
||||||
|
val meanAndDeviation = times.culledMeanAndDeviation()
|
||||||
|
println(times)
|
||||||
|
println("${meanAndDeviation.first} ± ${meanAndDeviation.second}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<Double>.culledMeanAndDeviation(): Pair<Double, Double> = when {
|
||||||
|
isEmpty() -> Double.NaN to Double.NaN
|
||||||
|
size == 1 || size == 2 -> this.meanAndDeviation()
|
||||||
|
else -> sorted().subList(1, size - 1).meanAndDeviation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<Double>.meanAndDeviation(): Pair<Double, Double> {
|
||||||
|
val mean = average()
|
||||||
|
return mean to sqrt(fold(0.0) { acc, value -> acc + (value - mean).squared() } / size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Double.squared() = this * this
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.looker.droidify.sync
|
package de.felitendo.felostore.sync
|
||||||
|
|
||||||
import com.looker.droidify.network.Downloader
|
import de.felitendo.felostore.network.Downloader
|
||||||
import com.looker.droidify.network.NetworkResponse
|
import de.felitendo.felostore.network.NetworkResponse
|
||||||
import com.looker.droidify.network.ProgressListener
|
import de.felitendo.felostore.network.ProgressListener
|
||||||
import com.looker.droidify.network.header.HeadersBuilder
|
import de.felitendo.felostore.network.header.HeadersBuilder
|
||||||
import com.looker.droidify.network.validation.FileValidator
|
import de.felitendo.felostore.network.validation.FileValidator
|
||||||
import com.looker.droidify.sync.common.assets
|
import de.felitendo.felostore.sync.common.assets
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
package com.looker.droidify.sync
|
package de.felitendo.felostore.sync
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.looker.droidify.domain.model.Repo
|
import de.felitendo.felostore.domain.model.Repo
|
||||||
import com.looker.droidify.sync.common.IndexJarValidator
|
import de.felitendo.felostore.sync.common.IndexJarValidator
|
||||||
import com.looker.droidify.sync.common.Izzy
|
import de.felitendo.felostore.sync.common.Izzy
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
import de.felitendo.felostore.sync.common.JsonParser
|
||||||
import com.looker.droidify.sync.common.assets
|
import de.felitendo.felostore.sync.common.assets
|
||||||
import com.looker.droidify.sync.common.downloadIndex
|
import de.felitendo.felostore.sync.common.downloadIndex
|
||||||
import com.looker.droidify.sync.common.benchmark
|
import de.felitendo.felostore.sync.common.benchmark
|
||||||
import com.looker.droidify.sync.v2.EntryParser
|
import de.felitendo.felostore.sync.v2.EntryParser
|
||||||
import com.looker.droidify.sync.v2.EntrySyncable
|
import de.felitendo.felostore.sync.v2.EntrySyncable
|
||||||
import com.looker.droidify.sync.v2.model.Entry
|
import de.felitendo.felostore.sync.v2.model.Entry
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
import de.felitendo.felostore.sync.v2.model.IndexV2
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@@ -44,7 +44,7 @@ class EntrySyncableTest {
|
|||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Before
|
@Before
|
||||||
fun before() {
|
fun before() {
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
context = InstrumentationRegistry.getInstrumentation().context
|
||||||
dispatcher = StandardTestDispatcher()
|
dispatcher = StandardTestDispatcher()
|
||||||
validator = IndexJarValidator(dispatcher)
|
validator = IndexJarValidator(dispatcher)
|
||||||
parser = EntryParser(dispatcher, JsonParser, validator)
|
parser = EntryParser(dispatcher, JsonParser, validator)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.looker.droidify.sync
|
package de.felitendo.felostore.sync
|
||||||
|
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
import de.felitendo.felostore.domain.model.Fingerprint
|
||||||
import java.util.jar.JarEntry
|
import java.util.jar.JarEntry
|
||||||
|
|
||||||
val FakeIndexValidator = object : IndexValidator {
|
val FakeIndexValidator = object : IndexValidator {
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
package com.looker.droidify.sync
|
package de.felitendo.felostore.sync
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.looker.droidify.domain.model.Repo
|
import de.felitendo.felostore.domain.model.Repo
|
||||||
import com.looker.droidify.sync.common.IndexJarValidator
|
import de.felitendo.felostore.sync.common.IndexJarValidator
|
||||||
import com.looker.droidify.sync.common.Izzy
|
import de.felitendo.felostore.sync.common.Izzy
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
import de.felitendo.felostore.sync.common.JsonParser
|
||||||
import com.looker.droidify.sync.common.benchmark
|
import de.felitendo.felostore.sync.common.downloadIndex
|
||||||
import com.looker.droidify.sync.common.downloadIndex
|
import de.felitendo.felostore.sync.common.benchmark
|
||||||
import com.looker.droidify.sync.common.toV2
|
import de.felitendo.felostore.sync.common.toV2
|
||||||
import com.looker.droidify.sync.v1.V1Parser
|
import de.felitendo.felostore.sync.v1.V1Parser
|
||||||
import com.looker.droidify.sync.v1.V1Syncable
|
import de.felitendo.felostore.sync.v1.V1Syncable
|
||||||
import com.looker.droidify.sync.v1.model.IndexV1
|
import de.felitendo.felostore.sync.v1.model.IndexV1
|
||||||
import com.looker.droidify.sync.v2.V2Parser
|
import de.felitendo.felostore.sync.v2.V2Parser
|
||||||
import com.looker.droidify.sync.v2.model.FileV2
|
import de.felitendo.felostore.sync.v2.model.FileV2
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
import de.felitendo.felostore.sync.v2.model.IndexV2
|
||||||
import com.looker.droidify.sync.v2.model.MetadataV2
|
import de.felitendo.felostore.sync.v2.model.MetadataV2
|
||||||
import com.looker.droidify.sync.v2.model.PackageV2
|
import de.felitendo.felostore.sync.v2.model.VersionV2
|
||||||
import com.looker.droidify.sync.v2.model.VersionV2
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@@ -29,8 +28,6 @@ import kotlin.test.Test
|
|||||||
import kotlin.test.assertContentEquals
|
import kotlin.test.assertContentEquals
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertNull
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class V1SyncableTest {
|
class V1SyncableTest {
|
||||||
@@ -45,7 +42,7 @@ class V1SyncableTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun before() {
|
fun before() {
|
||||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
context = InstrumentationRegistry.getInstrumentation().context
|
||||||
dispatcher = StandardTestDispatcher()
|
dispatcher = StandardTestDispatcher()
|
||||||
validator = IndexJarValidator(dispatcher)
|
validator = IndexJarValidator(dispatcher)
|
||||||
parser = V1Parser(dispatcher, JsonParser, validator)
|
parser = V1Parser(dispatcher, JsonParser, validator)
|
||||||
@@ -105,38 +102,9 @@ class V1SyncableTest {
|
|||||||
testIndexConversion("index-v1.jar", "index-v2-updated.json")
|
testIndexConversion("index-v1.jar", "index-v2-updated.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
fun targetPropertyTest() = runTest(dispatcher) {
|
fun v1tov2FDroidRepo() = runTest(dispatcher) {
|
||||||
val v2IzzyFile =
|
testIndexConversion("fdroid-index-v1.jar", "fdroid-index-v2.json")
|
||||||
FakeDownloader.downloadIndex(context, repo, "izzy-v2", "index-v2-updated.json")
|
|
||||||
val v2FdroidFile =
|
|
||||||
FakeDownloader.downloadIndex(context, repo, "fdroid-v2", "fdroid-index-v2.json")
|
|
||||||
val (_, v2Izzy) = v2Parser.parse(v2IzzyFile, repo)
|
|
||||||
val (_, v2Fdroid) = v2Parser.parse(v2FdroidFile, repo)
|
|
||||||
|
|
||||||
val performTest: (PackageV2) -> Unit = { data ->
|
|
||||||
print("lib: ")
|
|
||||||
println(data.metadata.liberapay)
|
|
||||||
print("donate: ")
|
|
||||||
println(data.metadata.donate)
|
|
||||||
print("bit: ")
|
|
||||||
println(data.metadata.bitcoin)
|
|
||||||
print("flattr: ")
|
|
||||||
println(data.metadata.flattrID)
|
|
||||||
print("Open: ")
|
|
||||||
println(data.metadata.openCollective)
|
|
||||||
print("LiteCoin: ")
|
|
||||||
println(data.metadata.litecoin)
|
|
||||||
}
|
|
||||||
|
|
||||||
v2Izzy.packages.forEach { (packageName, data) ->
|
|
||||||
println("Testing on Izzy $packageName")
|
|
||||||
performTest(data)
|
|
||||||
}
|
|
||||||
v2Fdroid.packages.forEach { (packageName, data) ->
|
|
||||||
println("Testing on FDroid $packageName")
|
|
||||||
performTest(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun testIndexConversion(
|
private suspend fun testIndexConversion(
|
||||||
@@ -284,8 +252,6 @@ private fun assertVersion(
|
|||||||
assertNotNull(foundVersion)
|
assertNotNull(foundVersion)
|
||||||
|
|
||||||
assertEquals(expectedVersion.added, foundVersion.added)
|
assertEquals(expectedVersion.added, foundVersion.added)
|
||||||
assertEquals(expectedVersion.file.sha256, foundVersion.file.sha256)
|
|
||||||
assertEquals(expectedVersion.file.size, foundVersion.file.size)
|
|
||||||
assertEquals(expectedVersion.file.name, foundVersion.file.name)
|
assertEquals(expectedVersion.file.name, foundVersion.file.name)
|
||||||
assertEquals(expectedVersion.src?.name, foundVersion.src?.name)
|
assertEquals(expectedVersion.src?.name, foundVersion.src?.name)
|
||||||
|
|
||||||
@@ -295,13 +261,7 @@ private fun assertVersion(
|
|||||||
assertEquals(expectedMan.versionCode, foundMan.versionCode)
|
assertEquals(expectedMan.versionCode, foundMan.versionCode)
|
||||||
assertEquals(expectedMan.versionName, foundMan.versionName)
|
assertEquals(expectedMan.versionName, foundMan.versionName)
|
||||||
assertEquals(expectedMan.maxSdkVersion, foundMan.maxSdkVersion)
|
assertEquals(expectedMan.maxSdkVersion, foundMan.maxSdkVersion)
|
||||||
assertNotNull(expectedMan.usesSdk)
|
|
||||||
assertNotNull(foundMan.usesSdk)
|
|
||||||
assertEquals(expectedMan.usesSdk, foundMan.usesSdk)
|
assertEquals(expectedMan.usesSdk, foundMan.usesSdk)
|
||||||
assertTrue(expectedMan.usesSdk.minSdkVersion >= 1)
|
|
||||||
assertTrue(expectedMan.usesSdk.targetSdkVersion >= 1)
|
|
||||||
assertTrue(foundMan.usesSdk.minSdkVersion >= 1)
|
|
||||||
assertTrue(foundMan.usesSdk.targetSdkVersion >= 1)
|
|
||||||
|
|
||||||
assertContentEquals(
|
assertContentEquals(
|
||||||
expectedMan.features.sortedBy { it.name },
|
expectedMan.features.sortedBy { it.name },
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.sync.common
|
package de.felitendo.felostore.sync.common
|
||||||
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
@@ -8,6 +8,11 @@ internal inline fun benchmark(
|
|||||||
extraMessage: String? = null,
|
extraMessage: String? = null,
|
||||||
block: () -> Long,
|
block: () -> Long,
|
||||||
): String {
|
): String {
|
||||||
|
if (extraMessage != null) {
|
||||||
|
println("=".repeat(50))
|
||||||
|
println(extraMessage)
|
||||||
|
println("=".repeat(50))
|
||||||
|
}
|
||||||
val times = DoubleArray(repetition)
|
val times = DoubleArray(repetition)
|
||||||
repeat(repetition) { iteration ->
|
repeat(repetition) { iteration ->
|
||||||
System.gc()
|
System.gc()
|
||||||
@@ -15,19 +20,11 @@ internal inline fun benchmark(
|
|||||||
times[iteration] = block().toDouble()
|
times[iteration] = block().toDouble()
|
||||||
}
|
}
|
||||||
val meanAndDeviation = times.culledMeanAndDeviation()
|
val meanAndDeviation = times.culledMeanAndDeviation()
|
||||||
return buildString(200) {
|
return buildString {
|
||||||
append("=".repeat(50))
|
append("=".repeat(50))
|
||||||
append("\n")
|
append("\n")
|
||||||
if (extraMessage != null) {
|
append(times.joinToString(" | "))
|
||||||
append(extraMessage)
|
append("\n")
|
||||||
append("\n")
|
|
||||||
append("=".repeat(50))
|
|
||||||
append("\n")
|
|
||||||
}
|
|
||||||
if (times.size > 1) {
|
|
||||||
append(times.joinToString(" | "))
|
|
||||||
append("\n")
|
|
||||||
}
|
|
||||||
append("${meanAndDeviation.first} ms ± ${meanAndDeviation.second.toFloat()} ms")
|
append("${meanAndDeviation.first} ms ± ${meanAndDeviation.second.toFloat()} ms")
|
||||||
append("\n")
|
append("\n")
|
||||||
append("=".repeat(50))
|
append("=".repeat(50))
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.looker.droidify.sync.common
|
package de.felitendo.felostore.sync.common
|
||||||
|
|
||||||
import com.looker.droidify.domain.model.Authentication
|
import de.felitendo.felostore.domain.model.Authentication
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
import de.felitendo.felostore.domain.model.Fingerprint
|
||||||
import com.looker.droidify.domain.model.Repo
|
import de.felitendo.felostore.domain.model.Repo
|
||||||
import com.looker.droidify.domain.model.VersionInfo
|
import de.felitendo.felostore.domain.model.VersionInfo
|
||||||
|
|
||||||
val Izzy = Repo(
|
val Izzy = Repo(
|
||||||
id = 1,
|
id = 1L,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
address = "https://apt.izzysoft.de/fdroid/repo",
|
address = "https://apt.izzysoft.de/fdroid/repo",
|
||||||
name = "IzzyOnDroid F-Droid Repo",
|
name = "IzzyOnDroid F-Droid Repo",
|
||||||
@@ -15,4 +15,6 @@ val Izzy = Repo(
|
|||||||
authentication = Authentication("", ""),
|
authentication = Authentication("", ""),
|
||||||
versionInfo = VersionInfo(0L, null),
|
versionInfo = VersionInfo(0L, null),
|
||||||
mirrors = emptyList(),
|
mirrors = emptyList(),
|
||||||
|
antiFeatures = emptyList(),
|
||||||
|
categories = emptyList(),
|
||||||
)
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.sync.common
|
package de.felitendo.felostore.sync.common
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@
|
|||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Droidify"
|
android:name=".FeloStore"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:banner="@drawable/tv_banner"
|
android:banner="@drawable/tv_banner"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".Droidify$BootReceiver"
|
android:name=".FeloStore$BootReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<data android:pathPattern="/.*/packages/.*" />
|
<data android:pathPattern="/.*/packages/.*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:host="droidify.eu.org" />
|
<data android:host="felostore.eu.org" />
|
||||||
<data android:pathPattern="/app/.*" />
|
<data android:pathPattern="/app/.*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
@file:OptIn(ExperimentalEncodingApi::class)
|
|
||||||
|
|
||||||
package com.looker.droidify.data.encryption
|
|
||||||
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.KeyGenerator
|
|
||||||
import javax.crypto.spec.IvParameterSpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
import kotlin.io.encoding.Base64
|
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
|
||||||
|
|
||||||
private const val KEY_SIZE = 256
|
|
||||||
private const val IV_SIZE = 16
|
|
||||||
private const val ALGORITHM = "AES"
|
|
||||||
private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
value class Key(val secretKey: ByteArray) {
|
|
||||||
|
|
||||||
val spec: SecretKeySpec
|
|
||||||
get() = SecretKeySpec(secretKey, ALGORITHM)
|
|
||||||
|
|
||||||
fun encrypt(input: String): Pair<Encrypted, ByteArray> {
|
|
||||||
val iv = generateIV()
|
|
||||||
val ivSpec = IvParameterSpec(iv)
|
|
||||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, spec, ivSpec)
|
|
||||||
val encrypted = cipher.doFinal(input.toByteArray())
|
|
||||||
return Encrypted(Base64.encode(encrypted)) to iv
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Before encrypting we convert it to a base64 string
|
|
||||||
* */
|
|
||||||
@JvmInline
|
|
||||||
value class Encrypted(val value: String) {
|
|
||||||
fun decrypt(key: Key, iv: ByteArray): String {
|
|
||||||
val iv = IvParameterSpec(iv)
|
|
||||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key.spec, iv)
|
|
||||||
val decrypted = cipher.doFinal(Base64.decode(value))
|
|
||||||
return String(decrypted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generateSecretKey(): ByteArray {
|
|
||||||
return with(KeyGenerator.getInstance(ALGORITHM)) {
|
|
||||||
init(KEY_SIZE)
|
|
||||||
generateKey().encoded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateIV(): ByteArray {
|
|
||||||
val iv = ByteArray(IV_SIZE)
|
|
||||||
val secureRandom = SecureRandom()
|
|
||||||
secureRandom.nextBytes(iv)
|
|
||||||
return iv
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.looker.droidify.data.local
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.BuiltInTypeConverters
|
|
||||||
import androidx.room.Database
|
|
||||||
import androidx.room.Room
|
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.TypeConverters
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
||||||
import com.looker.droidify.data.local.converters.Converters
|
|
||||||
import com.looker.droidify.data.local.converters.PermissionConverter
|
|
||||||
import com.looker.droidify.data.local.dao.AppDao
|
|
||||||
import com.looker.droidify.data.local.dao.AuthDao
|
|
||||||
import com.looker.droidify.data.local.dao.IndexDao
|
|
||||||
import com.looker.droidify.data.local.dao.RepoDao
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureAppRelation
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureEntity
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureRepoRelation
|
|
||||||
import com.looker.droidify.data.local.model.AppEntity
|
|
||||||
import com.looker.droidify.data.local.model.AuthenticationEntity
|
|
||||||
import com.looker.droidify.data.local.model.AuthorEntity
|
|
||||||
import com.looker.droidify.data.local.model.CategoryAppRelation
|
|
||||||
import com.looker.droidify.data.local.model.CategoryEntity
|
|
||||||
import com.looker.droidify.data.local.model.CategoryRepoRelation
|
|
||||||
import com.looker.droidify.data.local.model.DonateEntity
|
|
||||||
import com.looker.droidify.data.local.model.GraphicEntity
|
|
||||||
import com.looker.droidify.data.local.model.InstalledEntity
|
|
||||||
import com.looker.droidify.data.local.model.LinksEntity
|
|
||||||
import com.looker.droidify.data.local.model.MirrorEntity
|
|
||||||
import com.looker.droidify.data.local.model.RepoEntity
|
|
||||||
import com.looker.droidify.data.local.model.ScreenshotEntity
|
|
||||||
import com.looker.droidify.data.local.model.VersionEntity
|
|
||||||
|
|
||||||
@Database(
|
|
||||||
entities = [
|
|
||||||
AntiFeatureEntity::class,
|
|
||||||
AntiFeatureAppRelation::class,
|
|
||||||
AntiFeatureRepoRelation::class,
|
|
||||||
AuthenticationEntity::class,
|
|
||||||
AuthorEntity::class,
|
|
||||||
AppEntity::class,
|
|
||||||
CategoryEntity::class,
|
|
||||||
CategoryAppRelation::class,
|
|
||||||
CategoryRepoRelation::class,
|
|
||||||
DonateEntity::class,
|
|
||||||
GraphicEntity::class,
|
|
||||||
InstalledEntity::class,
|
|
||||||
LinksEntity::class,
|
|
||||||
MirrorEntity::class,
|
|
||||||
RepoEntity::class,
|
|
||||||
ScreenshotEntity::class,
|
|
||||||
VersionEntity::class,
|
|
||||||
],
|
|
||||||
version = 1,
|
|
||||||
)
|
|
||||||
@TypeConverters(
|
|
||||||
PermissionConverter::class,
|
|
||||||
Converters::class,
|
|
||||||
builtInTypeConverters = BuiltInTypeConverters(),
|
|
||||||
)
|
|
||||||
abstract class DroidifyDatabase : RoomDatabase() {
|
|
||||||
abstract fun appDao(): AppDao
|
|
||||||
abstract fun repoDao(): RepoDao
|
|
||||||
abstract fun authDao(): AuthDao
|
|
||||||
abstract fun indexDao(): IndexDao
|
|
||||||
}
|
|
||||||
|
|
||||||
fun droidifyDatabase(context: Context): DroidifyDatabase = Room
|
|
||||||
.databaseBuilder(
|
|
||||||
context = context,
|
|
||||||
klass = DroidifyDatabase::class.java,
|
|
||||||
name = "droidify_room",
|
|
||||||
)
|
|
||||||
.addCallback(
|
|
||||||
object : RoomDatabase.Callback() {
|
|
||||||
override fun onOpen(db: SupportSQLiteDatabase) {
|
|
||||||
super.onOpen(db)
|
|
||||||
db.query("PRAGMA synchronous = OFF")
|
|
||||||
db.query("PRAGMA journal_mode = WAL")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.converters
|
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
|
||||||
import com.looker.droidify.sync.v2.model.FileV2
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedIcon
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedString
|
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
|
||||||
import kotlinx.serialization.builtins.MapSerializer
|
|
||||||
import kotlinx.serialization.builtins.serializer
|
|
||||||
|
|
||||||
private val localizedStringSerializer =
|
|
||||||
MapSerializer(String.serializer(), String.serializer())
|
|
||||||
private val stringListSerializer = ListSerializer(String.serializer())
|
|
||||||
private val localizedIconSerializer =
|
|
||||||
MapSerializer(String.serializer(), FileV2.serializer())
|
|
||||||
private val mapOfLocalizedStringsSerializer =
|
|
||||||
MapSerializer(String.serializer(), localizedStringSerializer)
|
|
||||||
|
|
||||||
object Converters {
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fromLocalizedString(value: LocalizedString): String {
|
|
||||||
return JsonParser.encodeToString(localizedStringSerializer, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun toLocalizedString(value: String): LocalizedString {
|
|
||||||
return JsonParser.decodeFromString(localizedStringSerializer, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fromLocalizedIcon(value: LocalizedIcon?): String? {
|
|
||||||
return value?.let { JsonParser.encodeToString(localizedIconSerializer, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun toLocalizedIcon(value: String?): LocalizedIcon? {
|
|
||||||
return value?.let { JsonParser.decodeFromString(localizedIconSerializer, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fromLocalizedList(value: Map<String, LocalizedString>): String {
|
|
||||||
return JsonParser.encodeToString(mapOfLocalizedStringsSerializer, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun toLocalizedList(value: String): Map<String, LocalizedString> {
|
|
||||||
return JsonParser.decodeFromString(mapOfLocalizedStringsSerializer, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fromStringList(value: List<String>): String {
|
|
||||||
return JsonParser.encodeToString(stringListSerializer, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun toStringList(value: String): List<String> {
|
|
||||||
return JsonParser.decodeFromString(stringListSerializer, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.converters
|
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
|
||||||
import com.looker.droidify.sync.v2.model.PermissionV2
|
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
|
||||||
|
|
||||||
private val permissionListSerializer = ListSerializer(PermissionV2.serializer())
|
|
||||||
|
|
||||||
object PermissionConverter {
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun fromPermissionV2List(value: List<PermissionV2>): String {
|
|
||||||
return JsonParser.encodeToString(permissionListSerializer, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@TypeConverter
|
|
||||||
fun toPermissionV2List(value: String): List<PermissionV2> {
|
|
||||||
return JsonParser.decodeFromString(permissionListSerializer, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.RawQuery
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureAppRelation
|
|
||||||
import com.looker.droidify.data.local.model.AppEntity
|
|
||||||
import com.looker.droidify.data.local.model.AppEntityRelations
|
|
||||||
import com.looker.droidify.data.local.model.CategoryAppRelation
|
|
||||||
import com.looker.droidify.data.local.model.VersionEntity
|
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
|
||||||
import com.looker.droidify.sync.v2.model.DefaultName
|
|
||||||
import com.looker.droidify.sync.v2.model.Tag
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface AppDao {
|
|
||||||
|
|
||||||
@RawQuery(
|
|
||||||
observedEntities = [
|
|
||||||
AppEntity::class,
|
|
||||||
VersionEntity::class,
|
|
||||||
CategoryAppRelation::class,
|
|
||||||
AntiFeatureAppRelation::class,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
fun _rawStreamAppEntities(query: SimpleSQLiteQuery): Flow<List<AppEntity>>
|
|
||||||
|
|
||||||
@RawQuery
|
|
||||||
suspend fun _rawQueryAppEntities(query: SimpleSQLiteQuery): List<AppEntity>
|
|
||||||
|
|
||||||
fun stream(
|
|
||||||
sortOrder: SortOrder,
|
|
||||||
searchQuery: String? = null,
|
|
||||||
repoId: Int? = null,
|
|
||||||
categoriesToInclude: List<DefaultName>? = null,
|
|
||||||
categoriesToExclude: List<DefaultName>? = null,
|
|
||||||
antiFeaturesToInclude: List<Tag>? = null,
|
|
||||||
antiFeaturesToExclude: List<Tag>? = null,
|
|
||||||
): Flow<List<AppEntity>> = _rawStreamAppEntities(
|
|
||||||
searchQuery(
|
|
||||||
sortOrder = sortOrder,
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
repoId = repoId,
|
|
||||||
categoriesToInclude = categoriesToInclude,
|
|
||||||
categoriesToExclude = categoriesToExclude,
|
|
||||||
antiFeaturesToInclude = antiFeaturesToInclude,
|
|
||||||
antiFeaturesToExclude = antiFeaturesToExclude,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun query(
|
|
||||||
sortOrder: SortOrder,
|
|
||||||
searchQuery: String? = null,
|
|
||||||
repoId: Int? = null,
|
|
||||||
categoriesToInclude: List<DefaultName>? = null,
|
|
||||||
categoriesToExclude: List<DefaultName>? = null,
|
|
||||||
antiFeaturesToInclude: List<Tag>? = null,
|
|
||||||
antiFeaturesToExclude: List<Tag>? = null,
|
|
||||||
): List<AppEntity> = _rawQueryAppEntities(
|
|
||||||
searchQuery(
|
|
||||||
sortOrder = sortOrder,
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
repoId = repoId,
|
|
||||||
categoriesToInclude = categoriesToInclude,
|
|
||||||
categoriesToExclude = categoriesToExclude,
|
|
||||||
antiFeaturesToInclude = antiFeaturesToInclude,
|
|
||||||
antiFeaturesToExclude = antiFeaturesToExclude,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun searchQuery(
|
|
||||||
sortOrder: SortOrder,
|
|
||||||
searchQuery: String?,
|
|
||||||
repoId: Int?,
|
|
||||||
categoriesToInclude: List<DefaultName>?,
|
|
||||||
categoriesToExclude: List<DefaultName>?,
|
|
||||||
antiFeaturesToInclude: List<Tag>?,
|
|
||||||
antiFeaturesToExclude: List<Tag>?,
|
|
||||||
): SimpleSQLiteQuery {
|
|
||||||
val args = arrayListOf<Any?>()
|
|
||||||
|
|
||||||
val query = buildString(1024) {
|
|
||||||
append("SELECT DISTINCT app.* FROM app")
|
|
||||||
append(" LEFT JOIN version ON app.id = version.appId")
|
|
||||||
append(" LEFT JOIN category_app_relation ON app.id = category_app_relation.id")
|
|
||||||
append(" LEFT JOIN anti_features_app_relation ON app.id = anti_features_app_relation.appId")
|
|
||||||
append(" WHERE 1")
|
|
||||||
|
|
||||||
if (repoId != null) {
|
|
||||||
append(" AND app.repoId = ?")
|
|
||||||
args.add(repoId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (categoriesToInclude != null) {
|
|
||||||
append(" AND category_app_relation.defaultName IN (")
|
|
||||||
append(categoriesToInclude.joinToString(", ") { "?" })
|
|
||||||
append(")")
|
|
||||||
args.addAll(categoriesToInclude)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (categoriesToExclude != null) {
|
|
||||||
append(" AND category_app_relation.defaultName NOT IN (")
|
|
||||||
append(categoriesToExclude.joinToString(", ") { "?" })
|
|
||||||
append(")")
|
|
||||||
args.addAll(categoriesToExclude)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (antiFeaturesToInclude != null) {
|
|
||||||
append(" AND anti_features_app_relation.tag IN (")
|
|
||||||
append(antiFeaturesToInclude.joinToString(", ") { "?" })
|
|
||||||
append(")")
|
|
||||||
args.addAll(antiFeaturesToInclude)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (antiFeaturesToExclude != null) {
|
|
||||||
append(" AND anti_features_app_relation.tag NOT IN (")
|
|
||||||
append(antiFeaturesToExclude.joinToString(", ") { "?" })
|
|
||||||
append(")")
|
|
||||||
args.addAll(antiFeaturesToExclude)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchQuery != null) {
|
|
||||||
val searchPattern = "%${searchQuery}%"
|
|
||||||
append(
|
|
||||||
"""
|
|
||||||
AND (
|
|
||||||
app.name LIKE ?
|
|
||||||
OR app.summary LIKE ?
|
|
||||||
OR app.packageName LIKE ?
|
|
||||||
OR app.description LIKE ?
|
|
||||||
)""",
|
|
||||||
)
|
|
||||||
args.addAll(listOf(searchPattern, searchPattern, searchPattern, searchPattern))
|
|
||||||
}
|
|
||||||
|
|
||||||
append(" ORDER BY ")
|
|
||||||
|
|
||||||
// Weighting: name > summary > packageName > description
|
|
||||||
if (searchQuery != null) {
|
|
||||||
val searchPattern = "%${searchQuery}%"
|
|
||||||
append("(CASE WHEN app.name LIKE ? THEN 4 ELSE 0 END) + ")
|
|
||||||
append("(CASE WHEN app.summary LIKE ? THEN 3 ELSE 0 END) + ")
|
|
||||||
append("(CASE WHEN app.packageName LIKE ? THEN 2 ELSE 0 END) + ")
|
|
||||||
append("(CASE WHEN app.description LIKE ? THEN 1 ELSE 0 END) DESC, ")
|
|
||||||
args.addAll(listOf(searchPattern, searchPattern, searchPattern, searchPattern))
|
|
||||||
}
|
|
||||||
|
|
||||||
when (sortOrder) {
|
|
||||||
SortOrder.UPDATED -> append("app.lastUpdated DESC, ")
|
|
||||||
SortOrder.ADDED -> append("app.added DESC, ")
|
|
||||||
SortOrder.SIZE -> append("version.apk_size DESC, ")
|
|
||||||
SortOrder.NAME -> Unit
|
|
||||||
}
|
|
||||||
append("app.name COLLATE LOCALIZED ASC")
|
|
||||||
}
|
|
||||||
|
|
||||||
return SimpleSQLiteQuery(query, args.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query(
|
|
||||||
"""
|
|
||||||
SELECT app.*
|
|
||||||
FROM app
|
|
||||||
LEFT JOIN installed
|
|
||||||
ON app.packageName = installed.packageName
|
|
||||||
LEFT JOIN version
|
|
||||||
ON version.appId = app.id
|
|
||||||
WHERE installed.packageName IS NOT NULL
|
|
||||||
ORDER BY
|
|
||||||
CASE WHEN version.versionCode > installed.versionCode THEN 1 ELSE 2 END,
|
|
||||||
app.lastUpdated DESC,
|
|
||||||
app.name COLLATE LOCALIZED ASC
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
fun installedStream(): Flow<List<AppEntity>>
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
@Query("SELECT * FROM app WHERE packageName = :packageName")
|
|
||||||
fun queryAppEntity(packageName: String): Flow<List<AppEntityRelations>>
|
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM app")
|
|
||||||
suspend fun count(): Int
|
|
||||||
|
|
||||||
@Query("DELETE FROM app WHERE id = :id")
|
|
||||||
suspend fun delete(id: Int)
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.Query
|
|
||||||
import com.looker.droidify.data.local.model.AuthenticationEntity
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface AuthDao {
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insert(authentication: AuthenticationEntity)
|
|
||||||
|
|
||||||
@Query("SELECT * FROM authentication WHERE repoId = :repoId")
|
|
||||||
suspend fun getAuthentication(repoId: Int): AuthenticationEntity?
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import androidx.room.Update
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureAppRelation
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureEntity
|
|
||||||
import com.looker.droidify.data.local.model.AntiFeatureRepoRelation
|
|
||||||
import com.looker.droidify.data.local.model.AppEntity
|
|
||||||
import com.looker.droidify.data.local.model.AuthorEntity
|
|
||||||
import com.looker.droidify.data.local.model.CategoryAppRelation
|
|
||||||
import com.looker.droidify.data.local.model.CategoryEntity
|
|
||||||
import com.looker.droidify.data.local.model.CategoryRepoRelation
|
|
||||||
import com.looker.droidify.data.local.model.DonateEntity
|
|
||||||
import com.looker.droidify.data.local.model.GraphicEntity
|
|
||||||
import com.looker.droidify.data.local.model.LinksEntity
|
|
||||||
import com.looker.droidify.data.local.model.MirrorEntity
|
|
||||||
import com.looker.droidify.data.local.model.RepoEntity
|
|
||||||
import com.looker.droidify.data.local.model.ScreenshotEntity
|
|
||||||
import com.looker.droidify.data.local.model.VersionEntity
|
|
||||||
import com.looker.droidify.data.local.model.antiFeatureEntity
|
|
||||||
import com.looker.droidify.data.local.model.appEntity
|
|
||||||
import com.looker.droidify.data.local.model.authorEntity
|
|
||||||
import com.looker.droidify.data.local.model.categoryEntity
|
|
||||||
import com.looker.droidify.data.local.model.donateEntity
|
|
||||||
import com.looker.droidify.data.local.model.linkEntity
|
|
||||||
import com.looker.droidify.data.local.model.localizedGraphics
|
|
||||||
import com.looker.droidify.data.local.model.localizedScreenshots
|
|
||||||
import com.looker.droidify.data.local.model.mirrorEntity
|
|
||||||
import com.looker.droidify.data.local.model.repoEntity
|
|
||||||
import com.looker.droidify.data.local.model.versionEntities
|
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface IndexDao {
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun insertIndex(
|
|
||||||
fingerprint: Fingerprint,
|
|
||||||
index: IndexV2,
|
|
||||||
expectedRepoId: Int = 0,
|
|
||||||
) {
|
|
||||||
val repoId = upsertRepo(
|
|
||||||
index.repo.repoEntity(
|
|
||||||
id = expectedRepoId,
|
|
||||||
fingerprint = fingerprint,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
val antiFeatures = index.repo.antiFeatures.flatMap { (tag, feature) ->
|
|
||||||
feature.antiFeatureEntity(tag)
|
|
||||||
}
|
|
||||||
val categories = index.repo.categories.flatMap { (defaultName, category) ->
|
|
||||||
category.categoryEntity(defaultName)
|
|
||||||
}
|
|
||||||
val antiFeatureRepoRelations = antiFeatures.map { AntiFeatureRepoRelation(repoId, it.tag) }
|
|
||||||
val categoryRepoRelations = categories.map { CategoryRepoRelation(repoId, it.defaultName) }
|
|
||||||
val mirrors = index.repo.mirrors.map { it.mirrorEntity(repoId) }
|
|
||||||
insertAntiFeatures(antiFeatures)
|
|
||||||
insertAntiFeatureRepoRelation(antiFeatureRepoRelations)
|
|
||||||
insertCategories(categories)
|
|
||||||
insertCategoryRepoRelation(categoryRepoRelations)
|
|
||||||
insertMirror(mirrors)
|
|
||||||
index.packages.forEach { (packageName, packages) ->
|
|
||||||
val metadata = packages.metadata
|
|
||||||
val author = metadata.authorEntity()
|
|
||||||
val authorId = upsertAuthor(author)
|
|
||||||
val appId = appIdByPackageName(repoId, packageName) ?: insertApp(
|
|
||||||
appEntity = metadata.appEntity(
|
|
||||||
packageName = packageName,
|
|
||||||
repoId = repoId,
|
|
||||||
authorId = authorId,
|
|
||||||
),
|
|
||||||
).toInt()
|
|
||||||
val versions = packages.versionEntities(appId)
|
|
||||||
insertVersions(versions.keys.toList())
|
|
||||||
insertAntiFeatureAppRelation(versions.values.flatten())
|
|
||||||
val appCategories = packages.metadata.categories.map { CategoryAppRelation(appId, it) }
|
|
||||||
insertCategoryAppRelation(appCategories)
|
|
||||||
metadata.linkEntity(appId)?.let { insertLink(it) }
|
|
||||||
metadata.screenshots?.localizedScreenshots(appId)?.let { insertScreenshots(it) }
|
|
||||||
metadata.localizedGraphics(appId)?.let { insertGraphics(it) }
|
|
||||||
metadata.donateEntity(appId)?.let { insertDonate(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertRepo(repoEntity: RepoEntity): Long
|
|
||||||
|
|
||||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun updateRepo(repoEntity: RepoEntity): Int
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun upsertRepo(repoEntity: RepoEntity): Int {
|
|
||||||
val id = insertRepo(repoEntity)
|
|
||||||
return if (id == -1L) {
|
|
||||||
updateRepo(repoEntity)
|
|
||||||
} else {
|
|
||||||
id.toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertMirror(mirrors: List<MirrorEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertAntiFeatures(antiFeatures: List<AntiFeatureEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertCategories(categories: List<CategoryEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertAntiFeatureRepoRelation(crossRef: List<AntiFeatureRepoRelation>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertCategoryRepoRelation(crossRef: List<CategoryRepoRelation>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertApp(appEntity: AppEntity): Long
|
|
||||||
|
|
||||||
@Query("SELECT id FROM app WHERE packageName = :packageName AND repoId = :repoId LIMIT 1")
|
|
||||||
suspend fun appIdByPackageName(repoId: Int, packageName: String): Int?
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertAuthor(authorEntity: AuthorEntity): Long
|
|
||||||
|
|
||||||
@Query(
|
|
||||||
"""
|
|
||||||
SELECT id FROM author
|
|
||||||
WHERE
|
|
||||||
(:email IS NULL AND email IS NULL OR email = :email) AND
|
|
||||||
(:name IS NULL AND name IS NULL OR name = :name COLLATE NOCASE) AND
|
|
||||||
(:website IS NULL AND website IS NULL OR website = :website COLLATE NOCASE)
|
|
||||||
LIMIT 1
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
suspend fun authorId(
|
|
||||||
email: String?,
|
|
||||||
name: String?,
|
|
||||||
website: String?,
|
|
||||||
): Int?
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
suspend fun upsertAuthor(authorEntity: AuthorEntity): Int {
|
|
||||||
val id = insertAuthor(authorEntity)
|
|
||||||
return if (id == -1L) {
|
|
||||||
authorId(
|
|
||||||
email = authorEntity.email,
|
|
||||||
name = authorEntity.name,
|
|
||||||
website = authorEntity.website,
|
|
||||||
)!!.toInt()
|
|
||||||
} else {
|
|
||||||
id.toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertScreenshots(screenshotEntity: List<ScreenshotEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertLink(linksEntity: LinksEntity)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertGraphics(graphicEntity: List<GraphicEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertDonate(donateEntity: List<DonateEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertVersions(versions: List<VersionEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertCategoryAppRelation(crossRef: List<CategoryAppRelation>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertAntiFeatureAppRelation(crossRef: List<AntiFeatureAppRelation>)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
import com.looker.droidify.data.local.model.RepoEntity
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface RepoDao {
|
|
||||||
|
|
||||||
@Query("SELECT * FROM repository")
|
|
||||||
fun stream(): Flow<List<RepoEntity>>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM repository WHERE id = :repoId")
|
|
||||||
fun repo(repoId: Int): Flow<RepoEntity>
|
|
||||||
|
|
||||||
@Query("DELETE FROM repository WHERE id = :id")
|
|
||||||
suspend fun delete(id: Int)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import com.looker.droidify.sync.v2.model.AntiFeatureReason
|
|
||||||
import com.looker.droidify.sync.v2.model.AntiFeatureV2
|
|
||||||
import com.looker.droidify.sync.v2.model.Tag
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "anti_feature",
|
|
||||||
primaryKeys = ["tag", "locale"],
|
|
||||||
)
|
|
||||||
data class AntiFeatureEntity(
|
|
||||||
val icon: String?,
|
|
||||||
val name: String,
|
|
||||||
val description: String?,
|
|
||||||
val locale: String,
|
|
||||||
val tag: Tag,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "anti_feature_repo_relation",
|
|
||||||
primaryKeys = ["id", "tag"],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = RepoEntity::class,
|
|
||||||
childColumns = ["id"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class AntiFeatureRepoRelation(
|
|
||||||
@ColumnInfo("id")
|
|
||||||
val repoId: Int,
|
|
||||||
val tag: Tag,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "anti_features_app_relation",
|
|
||||||
primaryKeys = ["tag", "appId", "versionCode"],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["appId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class AntiFeatureAppRelation(
|
|
||||||
val tag: Tag,
|
|
||||||
val reason: AntiFeatureReason,
|
|
||||||
val appId: Int,
|
|
||||||
val versionCode: Long,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun AntiFeatureV2.antiFeatureEntity(
|
|
||||||
tag: Tag,
|
|
||||||
): List<AntiFeatureEntity> {
|
|
||||||
return name.map { (locale, localizedName) ->
|
|
||||||
AntiFeatureEntity(
|
|
||||||
icon = icon[locale]?.name,
|
|
||||||
name = localizedName,
|
|
||||||
description = description[locale],
|
|
||||||
tag = tag,
|
|
||||||
locale = locale,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Embedded
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.Junction
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import androidx.room.Relation
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedIcon
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedString
|
|
||||||
import com.looker.droidify.sync.v2.model.MetadataV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "app",
|
|
||||||
indices = [
|
|
||||||
Index("authorId"),
|
|
||||||
Index("repoId"),
|
|
||||||
Index("packageName"),
|
|
||||||
Index("packageName", "repoId", unique = true),
|
|
||||||
],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = RepoEntity::class,
|
|
||||||
childColumns = ["repoId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
ForeignKey(
|
|
||||||
entity = AuthorEntity::class,
|
|
||||||
childColumns = ["authorId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class AppEntity(
|
|
||||||
val added: Long,
|
|
||||||
val lastUpdated: Long,
|
|
||||||
val license: String?,
|
|
||||||
val name: LocalizedString,
|
|
||||||
val icon: LocalizedIcon?,
|
|
||||||
val preferredSigner: String?,
|
|
||||||
val summary: LocalizedString?,
|
|
||||||
val description: LocalizedString?,
|
|
||||||
val packageName: String,
|
|
||||||
val authorId: Int,
|
|
||||||
val repoId: Int,
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AppEntityRelations(
|
|
||||||
@Embedded val app: AppEntity,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "authorId",
|
|
||||||
entityColumn = "id",
|
|
||||||
)
|
|
||||||
val author: AuthorEntity,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "appId",
|
|
||||||
)
|
|
||||||
val links: LinksEntity?,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "defaultName",
|
|
||||||
associateBy = Junction(CategoryAppRelation::class),
|
|
||||||
)
|
|
||||||
val categories: List<CategoryEntity>,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "appId",
|
|
||||||
)
|
|
||||||
val graphics: List<GraphicEntity>?,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "appId",
|
|
||||||
)
|
|
||||||
val screenshots: List<ScreenshotEntity>?,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "id",
|
|
||||||
entityColumn = "appId",
|
|
||||||
)
|
|
||||||
val versions: List<VersionEntity>?,
|
|
||||||
@Relation(
|
|
||||||
parentColumn = "packageName",
|
|
||||||
entityColumn = "packageName",
|
|
||||||
)
|
|
||||||
val installed: InstalledEntity?,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MetadataV2.appEntity(
|
|
||||||
packageName: String,
|
|
||||||
repoId: Int,
|
|
||||||
authorId: Int,
|
|
||||||
) = AppEntity(
|
|
||||||
added = added,
|
|
||||||
lastUpdated = lastUpdated,
|
|
||||||
license = license,
|
|
||||||
name = name,
|
|
||||||
icon = icon,
|
|
||||||
preferredSigner = preferredSigner,
|
|
||||||
summary = summary,
|
|
||||||
description = description,
|
|
||||||
packageName = packageName,
|
|
||||||
authorId = authorId,
|
|
||||||
repoId = repoId,
|
|
||||||
)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.looker.droidify.data.encryption.Encrypted
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "authentication",
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = RepoEntity::class,
|
|
||||||
childColumns = ["repoId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class AuthenticationEntity(
|
|
||||||
val password: Encrypted,
|
|
||||||
val username: String,
|
|
||||||
val initializationVector: String,
|
|
||||||
@PrimaryKey
|
|
||||||
val repoId: Int,
|
|
||||||
)
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.looker.droidify.domain.model.Author
|
|
||||||
import com.looker.droidify.sync.v2.model.MetadataV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "author",
|
|
||||||
indices = [Index("email", "name", "website", unique = true)],
|
|
||||||
)
|
|
||||||
data class AuthorEntity(
|
|
||||||
val email: String?,
|
|
||||||
val name: String?,
|
|
||||||
val website: String?,
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MetadataV2.authorEntity() = AuthorEntity(
|
|
||||||
email = authorEmail,
|
|
||||||
name = authorName,
|
|
||||||
website = authorWebSite,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun AuthorEntity.toAuthor() = Author(
|
|
||||||
email = email,
|
|
||||||
name = name,
|
|
||||||
phone = null,
|
|
||||||
web = website,
|
|
||||||
id = id,
|
|
||||||
)
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
|
||||||
import com.looker.droidify.sync.v2.model.CategoryV2
|
|
||||||
import com.looker.droidify.sync.v2.model.DefaultName
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "category",
|
|
||||||
primaryKeys = ["defaultName", "locale"],
|
|
||||||
indices = [Index("defaultName")]
|
|
||||||
)
|
|
||||||
data class CategoryEntity(
|
|
||||||
val icon: String?,
|
|
||||||
val name: String,
|
|
||||||
val description: String?,
|
|
||||||
val locale: String,
|
|
||||||
val defaultName: DefaultName,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "category_repo_relation",
|
|
||||||
primaryKeys = ["id", "defaultName"],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = RepoEntity::class,
|
|
||||||
childColumns = ["id"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class CategoryRepoRelation(
|
|
||||||
@ColumnInfo("id")
|
|
||||||
val repoId: Int,
|
|
||||||
val defaultName: DefaultName,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "category_app_relation",
|
|
||||||
primaryKeys = ["id", "defaultName"],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["id"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class CategoryAppRelation(
|
|
||||||
@ColumnInfo("id")
|
|
||||||
val appId: Int,
|
|
||||||
val defaultName: DefaultName,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun CategoryV2.categoryEntity(
|
|
||||||
defaultName: DefaultName,
|
|
||||||
): List<CategoryEntity> {
|
|
||||||
return name.map { (locale, localizedName) ->
|
|
||||||
CategoryEntity(
|
|
||||||
icon = icon[locale]?.name,
|
|
||||||
name = localizedName,
|
|
||||||
description = description[locale],
|
|
||||||
defaultName = defaultName,
|
|
||||||
locale = locale,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.Index
|
|
||||||
import com.looker.droidify.domain.model.Donation
|
|
||||||
import com.looker.droidify.sync.v2.model.MetadataV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "donate",
|
|
||||||
primaryKeys = ["type", "appId"],
|
|
||||||
indices = [Index("appId")],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["appId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class DonateEntity(
|
|
||||||
@param:DonationType
|
|
||||||
val type: Int,
|
|
||||||
val value: String,
|
|
||||||
val appId: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MetadataV2.donateEntity(appId: Int): List<DonateEntity>? {
|
|
||||||
return buildList {
|
|
||||||
if (bitcoin != null) {
|
|
||||||
add(DonateEntity(BITCOIN_ADD, bitcoin, appId))
|
|
||||||
}
|
|
||||||
if (litecoin != null) {
|
|
||||||
add(DonateEntity(LITECOIN_ADD, litecoin, appId))
|
|
||||||
}
|
|
||||||
if (liberapay != null) {
|
|
||||||
add(DonateEntity(LIBERAPAY_ID, liberapay, appId))
|
|
||||||
}
|
|
||||||
if (openCollective != null) {
|
|
||||||
add(DonateEntity(OPEN_COLLECTIVE_ID, openCollective, appId))
|
|
||||||
}
|
|
||||||
if (flattrID != null) {
|
|
||||||
add(DonateEntity(FLATTR_ID, flattrID, appId))
|
|
||||||
}
|
|
||||||
if (!donate.isNullOrEmpty()) {
|
|
||||||
add(DonateEntity(REGULAR, donate.joinToString(STRING_LIST_SEPARATOR), appId))
|
|
||||||
}
|
|
||||||
}.ifEmpty { null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun List<DonateEntity>.toDonation(): Donation {
|
|
||||||
var bitcoinAddress: String? = null
|
|
||||||
var litecoinAddress: String? = null
|
|
||||||
var liberapayId: String? = null
|
|
||||||
var openCollectiveId: String? = null
|
|
||||||
var flattrId: String? = null
|
|
||||||
var regular: List<String>? = null
|
|
||||||
for (entity in this) {
|
|
||||||
when (entity.type) {
|
|
||||||
BITCOIN_ADD -> bitcoinAddress = entity.value
|
|
||||||
FLATTR_ID -> flattrId = entity.value
|
|
||||||
LIBERAPAY_ID -> liberapayId = entity.value
|
|
||||||
LITECOIN_ADD -> litecoinAddress = entity.value
|
|
||||||
OPEN_COLLECTIVE_ID -> openCollectiveId = entity.value
|
|
||||||
REGULAR -> regular = entity.value.split(STRING_LIST_SEPARATOR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Donation(
|
|
||||||
bitcoinAddress = bitcoinAddress,
|
|
||||||
litecoinAddress = litecoinAddress,
|
|
||||||
liberapayId = liberapayId,
|
|
||||||
openCollectiveId = openCollectiveId,
|
|
||||||
flattrId = flattrId,
|
|
||||||
regularUrl = regular,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val STRING_LIST_SEPARATOR = "&^%#@!"
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
@IntDef(
|
|
||||||
BITCOIN_ADD,
|
|
||||||
LITECOIN_ADD,
|
|
||||||
LIBERAPAY_ID,
|
|
||||||
OPEN_COLLECTIVE_ID,
|
|
||||||
FLATTR_ID,
|
|
||||||
REGULAR,
|
|
||||||
)
|
|
||||||
private annotation class DonationType
|
|
||||||
|
|
||||||
private const val BITCOIN_ADD = 0
|
|
||||||
private const val LITECOIN_ADD = 1
|
|
||||||
private const val LIBERAPAY_ID = 2
|
|
||||||
private const val OPEN_COLLECTIVE_ID = 3
|
|
||||||
private const val FLATTR_ID = 4
|
|
||||||
private const val REGULAR = 5
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.Index
|
|
||||||
import com.looker.droidify.domain.model.Graphics
|
|
||||||
import com.looker.droidify.sync.v2.model.MetadataV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "graphic",
|
|
||||||
primaryKeys = ["type", "locale", "appId"],
|
|
||||||
indices = [Index("appId", "locale")],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["appId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class GraphicEntity(
|
|
||||||
@param:GraphicType
|
|
||||||
val type: Int,
|
|
||||||
val url: String,
|
|
||||||
val locale: String,
|
|
||||||
val appId: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MetadataV2.localizedGraphics(appId: Int): List<GraphicEntity>? {
|
|
||||||
return buildList {
|
|
||||||
promoGraphic?.forEach { (locale, value) ->
|
|
||||||
add(GraphicEntity(PROMO_GRAPHIC, value.name, locale, appId))
|
|
||||||
}
|
|
||||||
featureGraphic?.forEach { (locale, value) ->
|
|
||||||
add(GraphicEntity(FEATURE_GRAPHIC, value.name, locale, appId))
|
|
||||||
}
|
|
||||||
tvBanner?.forEach { (locale, value) ->
|
|
||||||
add(GraphicEntity(TV_BANNER, value.name, locale, appId))
|
|
||||||
}
|
|
||||||
video?.forEach { (locale, value) ->
|
|
||||||
add(GraphicEntity(VIDEO, value, locale, appId))
|
|
||||||
}
|
|
||||||
}.ifEmpty { null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun List<GraphicEntity>.toGraphics(): Graphics {
|
|
||||||
var featureGraphic: String? = null
|
|
||||||
var promoGraphic: String? = null
|
|
||||||
var tvBanner: String? = null
|
|
||||||
var video: String? = null
|
|
||||||
|
|
||||||
for (entity in this) {
|
|
||||||
when (entity.type) {
|
|
||||||
FEATURE_GRAPHIC -> featureGraphic = entity.url
|
|
||||||
PROMO_GRAPHIC -> promoGraphic = entity.url
|
|
||||||
TV_BANNER -> tvBanner = entity.url
|
|
||||||
VIDEO -> video = entity.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Graphics(
|
|
||||||
featureGraphic = featureGraphic,
|
|
||||||
promoGraphic = promoGraphic,
|
|
||||||
tvBanner = tvBanner,
|
|
||||||
video = video,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
@IntDef(
|
|
||||||
VIDEO,
|
|
||||||
TV_BANNER,
|
|
||||||
PROMO_GRAPHIC,
|
|
||||||
FEATURE_GRAPHIC,
|
|
||||||
)
|
|
||||||
annotation class GraphicType
|
|
||||||
|
|
||||||
private const val VIDEO = 0
|
|
||||||
private const val TV_BANNER = 1
|
|
||||||
private const val PROMO_GRAPHIC = 2
|
|
||||||
private const val FEATURE_GRAPHIC = 3
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity("installed")
|
|
||||||
data class InstalledEntity(
|
|
||||||
val versionCode: String,
|
|
||||||
val versionName: String,
|
|
||||||
val signature: String,
|
|
||||||
@PrimaryKey
|
|
||||||
val packageName: String,
|
|
||||||
)
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.looker.droidify.domain.model.Links
|
|
||||||
import com.looker.droidify.sync.v2.model.MetadataV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "link",
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["appId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class LinksEntity(
|
|
||||||
val changelog: String?,
|
|
||||||
val issueTracker: String?,
|
|
||||||
val translation: String?,
|
|
||||||
val sourceCode: String?,
|
|
||||||
val webSite: String?,
|
|
||||||
@PrimaryKey
|
|
||||||
val appId: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun MetadataV2.isLinkNull(): Boolean {
|
|
||||||
return changelog == null &&
|
|
||||||
issueTracker == null &&
|
|
||||||
translation == null &&
|
|
||||||
sourceCode == null &&
|
|
||||||
webSite == null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MetadataV2.linkEntity(appId: Int) = if (!isLinkNull()) {
|
|
||||||
LinksEntity(
|
|
||||||
appId = appId,
|
|
||||||
changelog = changelog,
|
|
||||||
issueTracker = issueTracker,
|
|
||||||
translation = translation,
|
|
||||||
sourceCode = sourceCode,
|
|
||||||
webSite = webSite,
|
|
||||||
)
|
|
||||||
} else null
|
|
||||||
|
|
||||||
fun LinksEntity.toLinks() = Links(
|
|
||||||
changelog = changelog,
|
|
||||||
issueTracker = issueTracker,
|
|
||||||
translation = translation,
|
|
||||||
sourceCode = sourceCode,
|
|
||||||
webSite = webSite,
|
|
||||||
)
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.looker.droidify.sync.v2.model.MirrorV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "mirror",
|
|
||||||
indices = [Index("repoId")],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = RepoEntity::class,
|
|
||||||
childColumns = ["repoId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
data class MirrorEntity(
|
|
||||||
val url: String,
|
|
||||||
val countryCode: String?,
|
|
||||||
val isPrimary: Boolean,
|
|
||||||
val repoId: Int,
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MirrorV2.mirrorEntity(repoId: Int) = MirrorEntity(
|
|
||||||
url = url,
|
|
||||||
countryCode = countryCode,
|
|
||||||
isPrimary = isPrimary == true,
|
|
||||||
repoId = repoId,
|
|
||||||
)
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.looker.droidify.domain.model.Authentication
|
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
|
||||||
import com.looker.droidify.domain.model.Repo
|
|
||||||
import com.looker.droidify.domain.model.VersionInfo
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedIcon
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedString
|
|
||||||
import com.looker.droidify.sync.v2.model.RepoV2
|
|
||||||
import com.looker.droidify.sync.v2.model.localizedValue
|
|
||||||
|
|
||||||
@Entity(tableName = "repository")
|
|
||||||
data class RepoEntity(
|
|
||||||
val icon: LocalizedIcon?,
|
|
||||||
val address: String,
|
|
||||||
val name: LocalizedString,
|
|
||||||
val description: LocalizedString,
|
|
||||||
val fingerprint: Fingerprint,
|
|
||||||
val timestamp: Long,
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun RepoV2.repoEntity(
|
|
||||||
id: Int,
|
|
||||||
fingerprint: Fingerprint,
|
|
||||||
) = RepoEntity(
|
|
||||||
id = id,
|
|
||||||
icon = icon,
|
|
||||||
address = address,
|
|
||||||
name = name,
|
|
||||||
description = description,
|
|
||||||
timestamp = timestamp,
|
|
||||||
fingerprint = fingerprint,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun RepoEntity.toRepo(
|
|
||||||
locale: String,
|
|
||||||
mirrors: List<String>,
|
|
||||||
enabled: Boolean,
|
|
||||||
authentication: Authentication? = null,
|
|
||||||
) = Repo(
|
|
||||||
name = name.localizedValue(locale) ?: "Unknown",
|
|
||||||
description = description.localizedValue(locale) ?: "Unknown",
|
|
||||||
fingerprint = fingerprint,
|
|
||||||
authentication = authentication,
|
|
||||||
enabled = enabled,
|
|
||||||
address = address,
|
|
||||||
versionInfo = VersionInfo(timestamp = timestamp, etag = null),
|
|
||||||
mirrors = mirrors,
|
|
||||||
id = id,
|
|
||||||
)
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.annotation.IntDef
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.Index
|
|
||||||
import com.looker.droidify.domain.model.Screenshots
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedFiles
|
|
||||||
import com.looker.droidify.sync.v2.model.ScreenshotsV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "screenshot",
|
|
||||||
primaryKeys = ["path", "type", "locale", "appId"],
|
|
||||||
indices = [Index("appId", "locale")],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["appId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class ScreenshotEntity(
|
|
||||||
val path: String,
|
|
||||||
@param:ScreenshotType
|
|
||||||
val type: Int,
|
|
||||||
val locale: String,
|
|
||||||
val appId: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun ScreenshotsV2.localizedScreenshots(appId: Int): List<ScreenshotEntity> {
|
|
||||||
if (isNull) return emptyList()
|
|
||||||
val screenshots = mutableListOf<ScreenshotEntity>()
|
|
||||||
|
|
||||||
val screenshotIterator: (Int, LocalizedFiles?) -> Unit = { type, localizedFiles ->
|
|
||||||
localizedFiles?.forEach { (locale, files) ->
|
|
||||||
for ((path, _, _) in files) {
|
|
||||||
screenshots.add(
|
|
||||||
ScreenshotEntity(
|
|
||||||
locale = locale,
|
|
||||||
appId = appId,
|
|
||||||
type = type,
|
|
||||||
path = path,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screenshotIterator(PHONE, phone)
|
|
||||||
screenshotIterator(SEVEN_INCH, sevenInch)
|
|
||||||
screenshotIterator(TEN_INCH, tenInch)
|
|
||||||
screenshotIterator(WEAR, wear)
|
|
||||||
screenshotIterator(TV, tv)
|
|
||||||
return screenshots
|
|
||||||
}
|
|
||||||
|
|
||||||
fun List<ScreenshotEntity>.toScreenshots(): Screenshots {
|
|
||||||
val phone = mutableListOf<String>()
|
|
||||||
val sevenInch = mutableListOf<String>()
|
|
||||||
val tenInch = mutableListOf<String>()
|
|
||||||
val wear = mutableListOf<String>()
|
|
||||||
val tv = mutableListOf<String>()
|
|
||||||
for (index in this.indices) {
|
|
||||||
val entity = get(index)
|
|
||||||
when (entity.type) {
|
|
||||||
PHONE -> phone.add(entity.path)
|
|
||||||
SEVEN_INCH -> sevenInch.add(entity.path)
|
|
||||||
TEN_INCH -> tenInch.add(entity.path)
|
|
||||||
TV -> tv.add(entity.path)
|
|
||||||
WEAR -> wear.add(entity.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Screenshots(
|
|
||||||
phone = phone,
|
|
||||||
sevenInch = sevenInch,
|
|
||||||
tenInch = tenInch,
|
|
||||||
wear = wear,
|
|
||||||
tv = tv,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
@IntDef(
|
|
||||||
PHONE,
|
|
||||||
SEVEN_INCH,
|
|
||||||
TEN_INCH,
|
|
||||||
WEAR,
|
|
||||||
TV,
|
|
||||||
)
|
|
||||||
private annotation class ScreenshotType
|
|
||||||
|
|
||||||
private const val PHONE = 0
|
|
||||||
private const val SEVEN_INCH = 1
|
|
||||||
private const val TEN_INCH = 2
|
|
||||||
private const val WEAR = 3
|
|
||||||
private const val TV = 4
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package com.looker.droidify.data.local.model
|
|
||||||
|
|
||||||
import androidx.room.Embedded
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.ForeignKey.Companion.CASCADE
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.looker.droidify.sync.v2.model.ApkFileV2
|
|
||||||
import com.looker.droidify.sync.v2.model.FileV2
|
|
||||||
import com.looker.droidify.sync.v2.model.LocalizedString
|
|
||||||
import com.looker.droidify.sync.v2.model.PackageV2
|
|
||||||
import com.looker.droidify.sync.v2.model.PermissionV2
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "version",
|
|
||||||
indices = [Index("appId")],
|
|
||||||
foreignKeys = [
|
|
||||||
ForeignKey(
|
|
||||||
entity = AppEntity::class,
|
|
||||||
childColumns = ["appId"],
|
|
||||||
parentColumns = ["id"],
|
|
||||||
onDelete = CASCADE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
data class VersionEntity(
|
|
||||||
val added: Long,
|
|
||||||
val whatsNew: LocalizedString,
|
|
||||||
val versionName: String,
|
|
||||||
val versionCode: Long,
|
|
||||||
val maxSdkVersion: Int?,
|
|
||||||
val minSdkVersion: Int,
|
|
||||||
val targetSdkVersion: Int,
|
|
||||||
@Embedded("apk_")
|
|
||||||
val apk: ApkFileV2,
|
|
||||||
@Embedded("src_")
|
|
||||||
val src: FileV2?,
|
|
||||||
val features: List<String>,
|
|
||||||
val nativeCode: List<String>,
|
|
||||||
val permissions: List<PermissionV2>,
|
|
||||||
val permissionsSdk23: List<PermissionV2>,
|
|
||||||
val appId: Int,
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
|
||||||
val id: Int = 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun PackageV2.versionEntities(appId: Int): Map<VersionEntity, List<AntiFeatureAppRelation>> {
|
|
||||||
return versions.map { (_, version) ->
|
|
||||||
VersionEntity(
|
|
||||||
added = version.added,
|
|
||||||
whatsNew = version.whatsNew,
|
|
||||||
versionName = version.manifest.versionName,
|
|
||||||
versionCode = version.manifest.versionCode,
|
|
||||||
maxSdkVersion = version.manifest.maxSdkVersion,
|
|
||||||
minSdkVersion = version.manifest.usesSdk?.minSdkVersion ?: -1,
|
|
||||||
targetSdkVersion = version.manifest.usesSdk?.targetSdkVersion ?: -1,
|
|
||||||
apk = version.file,
|
|
||||||
src = version.src,
|
|
||||||
features = version.manifest.features.map { it.name },
|
|
||||||
nativeCode = version.manifest.nativecode,
|
|
||||||
permissions = version.manifest.usesPermission,
|
|
||||||
permissionsSdk23 = version.manifest.usesPermissionSdk23,
|
|
||||||
appId = appId,
|
|
||||||
) to version.antiFeatures.map { (tag, reason) ->
|
|
||||||
AntiFeatureAppRelation(
|
|
||||||
tag = tag,
|
|
||||||
reason = reason,
|
|
||||||
appId = appId,
|
|
||||||
versionCode = version.manifest.versionCode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.toMap()
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
package com.looker.droidify.database.table
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
|
||||||
import androidx.core.database.sqlite.transaction
|
|
||||||
import com.looker.droidify.database.Database.RepositoryAdapter
|
|
||||||
import com.looker.droidify.database.Database.Schema
|
|
||||||
import com.looker.droidify.database.Database.jsonParse
|
|
||||||
import com.looker.droidify.database.Database.query
|
|
||||||
import com.looker.droidify.index.OemRepositoryParser
|
|
||||||
import com.looker.droidify.model.Repository
|
|
||||||
import com.looker.droidify.utility.common.extension.asSequence
|
|
||||||
import com.looker.droidify.utility.common.extension.firstOrNull
|
|
||||||
import com.looker.droidify.utility.serialization.repository
|
|
||||||
|
|
||||||
private const val DB_LEGACY_NAME = "droidify"
|
|
||||||
|
|
||||||
private const val DB_LEGACY_VERSION = 6
|
|
||||||
|
|
||||||
class DatabaseHelper(context: Context) :
|
|
||||||
SQLiteOpenHelper(context, DB_LEGACY_NAME, null, DB_LEGACY_VERSION) {
|
|
||||||
var created = false
|
|
||||||
private set
|
|
||||||
var updated = false
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
|
||||||
// Create all tables
|
|
||||||
db.execSQL(Schema.Repository.formatCreateTable(Schema.Repository.name))
|
|
||||||
db.execSQL(Schema.Product.formatCreateTable(Schema.Product.name))
|
|
||||||
db.execSQL(Schema.Category.formatCreateTable(Schema.Category.name))
|
|
||||||
|
|
||||||
// Add default repositories for new database
|
|
||||||
db.addDefaultRepositories()
|
|
||||||
db.addOemRepositories()
|
|
||||||
this.created = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
|
||||||
db.removeRepositories()
|
|
||||||
db.addNewlyAddedRepositories()
|
|
||||||
db.addOemRepositories()
|
|
||||||
this.updated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
|
||||||
// Handle database downgrades if needed
|
|
||||||
onUpgrade(db, oldVersion, newVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOpen(db: SQLiteDatabase) {
|
|
||||||
// Handle memory tables and indexes
|
|
||||||
db.execSQL("ATTACH DATABASE ':memory:' AS memory")
|
|
||||||
handleTables(db, Schema.Installed, Schema.Lock)
|
|
||||||
handleIndexes(
|
|
||||||
db,
|
|
||||||
Schema.Repository,
|
|
||||||
Schema.Product,
|
|
||||||
Schema.Category,
|
|
||||||
Schema.Installed,
|
|
||||||
Schema.Lock,
|
|
||||||
)
|
|
||||||
dropOldTables(db, Schema.Repository, Schema.Product, Schema.Category)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SQLiteDatabase.addOemRepositories() {
|
|
||||||
transaction {
|
|
||||||
OemRepositoryParser
|
|
||||||
.getSystemDefaultRepos()
|
|
||||||
?.forEach { repo -> RepositoryAdapter.put(repo, database = this) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SQLiteDatabase.addDefaultRepositories() {
|
|
||||||
// Add all default repositories for new database
|
|
||||||
transaction {
|
|
||||||
(Repository.defaultRepositories + Repository.newlyAdded)
|
|
||||||
.sortedBy { it.name }
|
|
||||||
.forEach { repo -> RepositoryAdapter.put(repo, database = this) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SQLiteDatabase.addNewlyAddedRepositories() {
|
|
||||||
// Add only newly added repositories, checking for existing ones
|
|
||||||
val existingRepos = query(
|
|
||||||
Schema.Repository.name,
|
|
||||||
columns = arrayOf(Schema.Repository.ROW_DATA),
|
|
||||||
selection = null,
|
|
||||||
signal = null,
|
|
||||||
).use { cursor ->
|
|
||||||
cursor.asSequence().mapNotNull {
|
|
||||||
val dataIndex = it.getColumnIndexOrThrow(Schema.Repository.ROW_DATA)
|
|
||||||
val data = it.getBlob(dataIndex)
|
|
||||||
|
|
||||||
try {
|
|
||||||
data.jsonParse { json -> json.repository() }.address
|
|
||||||
} catch (_: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add repositories that don't already exist
|
|
||||||
val reposToAdd = Repository.newlyAdded.filter { repo ->
|
|
||||||
repo.address !in existingRepos
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reposToAdd.isNotEmpty()) {
|
|
||||||
transaction {
|
|
||||||
reposToAdd.forEach { repo ->
|
|
||||||
RepositoryAdapter.put(repo, database = this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun SQLiteDatabase.removeRepositories() {
|
|
||||||
// Remove repositories that are in the toRemove list
|
|
||||||
val reposToRemove = Repository.toRemove
|
|
||||||
if (reposToRemove.isEmpty()) return
|
|
||||||
|
|
||||||
// Get all repositories with their IDs and addresses
|
|
||||||
val existingRepos = query(
|
|
||||||
Schema.Repository.name,
|
|
||||||
columns = arrayOf(Schema.Repository.ROW_ID, Schema.Repository.ROW_DATA),
|
|
||||||
selection = null,
|
|
||||||
signal = null,
|
|
||||||
).use { cursor ->
|
|
||||||
cursor.asSequence().mapNotNull {
|
|
||||||
val idIndex = it.getColumnIndexOrThrow(Schema.Repository.ROW_ID)
|
|
||||||
val dataIndex = it.getColumnIndexOrThrow(Schema.Repository.ROW_DATA)
|
|
||||||
val id = it.getLong(idIndex)
|
|
||||||
val data = it.getBlob(dataIndex)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val repo = data.jsonParse { json -> json.repository() }
|
|
||||||
id to repo.address
|
|
||||||
} catch (_: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find repositories to remove
|
|
||||||
val reposToRemoveIds = existingRepos.filter { (_, address) ->
|
|
||||||
address in reposToRemove
|
|
||||||
}.keys
|
|
||||||
|
|
||||||
if (reposToRemoveIds.isNotEmpty()) {
|
|
||||||
transaction {
|
|
||||||
reposToRemoveIds.forEach { repoId ->
|
|
||||||
// Directly update the database to mark repository as deleted
|
|
||||||
update(
|
|
||||||
Schema.Repository.name,
|
|
||||||
android.content.ContentValues().apply {
|
|
||||||
put(Schema.Repository.ROW_DELETED, 1)
|
|
||||||
},
|
|
||||||
"${Schema.Repository.ROW_ID} = ?",
|
|
||||||
arrayOf(repoId.toString()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleTables(db: SQLiteDatabase, vararg tables: Table): Boolean {
|
|
||||||
val shouldRecreate = tables.any { table ->
|
|
||||||
val sql = db.query(
|
|
||||||
"${table.databasePrefix}sqlite_master",
|
|
||||||
columns = arrayOf("sql"),
|
|
||||||
selection = Pair("type = ? AND name = ?", arrayOf("table", table.innerName)),
|
|
||||||
).use { it.firstOrNull()?.getString(0) }.orEmpty()
|
|
||||||
table.formatCreateTable(table.innerName) != sql
|
|
||||||
}
|
|
||||||
return shouldRecreate && run {
|
|
||||||
val shouldVacuum = tables.map {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS ${it.name}")
|
|
||||||
db.execSQL(it.formatCreateTable(it.name))
|
|
||||||
!it.memory
|
|
||||||
}
|
|
||||||
if (shouldVacuum.any { it } && !db.inTransaction()) {
|
|
||||||
db.execSQL("VACUUM")
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) {
|
|
||||||
val shouldVacuum = tables.map { table ->
|
|
||||||
val sqls = db.query(
|
|
||||||
"${table.databasePrefix}sqlite_master",
|
|
||||||
columns = arrayOf("name", "sql"),
|
|
||||||
selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", table.innerName)),
|
|
||||||
)
|
|
||||||
.use { cursor ->
|
|
||||||
cursor.asSequence()
|
|
||||||
.mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
.filter { !it.first.startsWith("sqlite_") }
|
|
||||||
val createIndexes = table.createIndexPairFormatted?.let { listOf(it) }.orEmpty()
|
|
||||||
createIndexes.map { it.first } != sqls.map { it.second } && run {
|
|
||||||
for (name in sqls.map { it.first }) {
|
|
||||||
db.execSQL("DROP INDEX IF EXISTS $name")
|
|
||||||
}
|
|
||||||
for (createIndexPair in createIndexes) {
|
|
||||||
db.execSQL(createIndexPair.second)
|
|
||||||
}
|
|
||||||
!table.memory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldVacuum.any { it } && !db.inTransaction()) {
|
|
||||||
db.execSQL("VACUUM")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) {
|
|
||||||
val tables = db.query(
|
|
||||||
"sqlite_master",
|
|
||||||
columns = arrayOf("name"),
|
|
||||||
selection = Pair("type = ?", arrayOf("table")),
|
|
||||||
)
|
|
||||||
.use { cursor -> cursor.asSequence().mapNotNull { it.getString(0) }.toList() }
|
|
||||||
.filter { !it.startsWith("sqlite_") && !it.startsWith("android_") }
|
|
||||||
.toSet() - neededTables.mapNotNull { if (it.memory) null else it.name }.toSet()
|
|
||||||
if (tables.isNotEmpty()) {
|
|
||||||
for (table in tables) {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS $table")
|
|
||||||
}
|
|
||||||
if (!db.inTransaction()) {
|
|
||||||
db.execSQL("VACUUM")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.looker.droidify.database.table
|
|
||||||
|
|
||||||
import com.looker.droidify.database.trimAndJoin
|
|
||||||
|
|
||||||
interface Table {
|
|
||||||
val memory: Boolean
|
|
||||||
val innerName: String
|
|
||||||
val createTable: String
|
|
||||||
val createIndex: String?
|
|
||||||
get() = null
|
|
||||||
|
|
||||||
val databasePrefix: String
|
|
||||||
get() = if (memory) "memory." else ""
|
|
||||||
|
|
||||||
val name: String
|
|
||||||
get() = "$databasePrefix$innerName"
|
|
||||||
|
|
||||||
fun formatCreateTable(name: String): String {
|
|
||||||
return buildString(128) {
|
|
||||||
append("CREATE TABLE ")
|
|
||||||
append(name)
|
|
||||||
append(" (")
|
|
||||||
trimAndJoin(createTable)
|
|
||||||
append(")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val createIndexPairFormatted: Pair<String, String>?
|
|
||||||
get() = createIndex?.let {
|
|
||||||
Pair(
|
|
||||||
"CREATE INDEX ${innerName}_index ON $innerName ($it)",
|
|
||||||
"CREATE INDEX ${name}_index ON $innerName ($it)",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.looker.droidify.datastore.model
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
sealed class LegacyInstallerComponent {
|
|
||||||
@Serializable
|
|
||||||
object Unspecified : LegacyInstallerComponent()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
object AlwaysChoose : LegacyInstallerComponent()
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Component(
|
|
||||||
val clazz: String,
|
|
||||||
val activity: String,
|
|
||||||
) : LegacyInstallerComponent() {
|
|
||||||
fun update(
|
|
||||||
newClazz: String? = null,
|
|
||||||
newActivity: String? = null,
|
|
||||||
): Component = copy(
|
|
||||||
clazz = newClazz ?: clazz,
|
|
||||||
activity = newActivity ?: activity
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.looker.droidify.datastore.model
|
|
||||||
|
|
||||||
// todo: Add Support for sorting by size
|
|
||||||
enum class SortOrder {
|
|
||||||
UPDATED,
|
|
||||||
ADDED,
|
|
||||||
NAME,
|
|
||||||
SIZE,
|
|
||||||
}
|
|
||||||
|
|
||||||
fun supportedSortOrders(): List<SortOrder> = listOf(SortOrder.UPDATED, SortOrder.ADDED, SortOrder.NAME)
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package com.looker.droidify.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.looker.droidify.data.local.DroidifyDatabase
|
|
||||||
import com.looker.droidify.data.local.dao.AppDao
|
|
||||||
import com.looker.droidify.data.local.dao.AuthDao
|
|
||||||
import com.looker.droidify.data.local.dao.IndexDao
|
|
||||||
import com.looker.droidify.data.local.dao.RepoDao
|
|
||||||
import com.looker.droidify.data.local.droidifyDatabase
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object DatabaseModule {
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideDatabase(
|
|
||||||
@ApplicationContext
|
|
||||||
context: Context,
|
|
||||||
): DroidifyDatabase = droidifyDatabase(context)
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideAppDao(
|
|
||||||
db: DroidifyDatabase,
|
|
||||||
): AppDao = db.appDao()
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideRepoDao(
|
|
||||||
db: DroidifyDatabase,
|
|
||||||
): RepoDao = db.repoDao()
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideAuthDao(
|
|
||||||
db: DroidifyDatabase,
|
|
||||||
): AuthDao = db.authDao()
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideIndexDao(
|
|
||||||
db: DroidifyDatabase,
|
|
||||||
): IndexDao = db.indexDao()
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.looker.droidify.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.looker.droidify.sync.LocalSyncable
|
|
||||||
import com.looker.droidify.sync.Syncable
|
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object SyncableModule {
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideSyncable(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
): Syncable<IndexV2> = LocalSyncable(context)
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.looker.droidify.index
|
|
||||||
|
|
||||||
import android.util.Xml
|
|
||||||
import com.looker.droidify.domain.model.fingerprint
|
|
||||||
import com.looker.droidify.model.Repository
|
|
||||||
import com.looker.droidify.model.Repository.Companion.defaultRepository
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct copy of implementation from https://github.com/NeoApplications/Neo-Store/blob/master/src/main/kotlin/com/machiav3lli/fdroid/data/database/entity/Repository.kt
|
|
||||||
* */
|
|
||||||
object OemRepositoryParser {
|
|
||||||
|
|
||||||
private val rootDirs = arrayOf("/system", "/product", "/vendor", "/odm", "/oem")
|
|
||||||
private val supportedPackageNames = arrayOf("com.looker.droidify", "org.fdroid.fdroid")
|
|
||||||
private const val FILE_NAME = "additional_repos.xml"
|
|
||||||
|
|
||||||
fun getSystemDefaultRepos() = rootDirs.flatMap { rootDir ->
|
|
||||||
supportedPackageNames.map { packageName -> "$rootDir/etc/$packageName/$FILE_NAME" }
|
|
||||||
}.flatMap { path ->
|
|
||||||
val file = File(path)
|
|
||||||
if (file.exists()) parse(file.inputStream()) else emptyList()
|
|
||||||
}.takeIf { it.isNotEmpty() }
|
|
||||||
|
|
||||||
fun parse(inputStream: InputStream): List<Repository> = with(Xml.newPullParser()) {
|
|
||||||
val repoItems = mutableListOf<String>()
|
|
||||||
inputStream.use { input ->
|
|
||||||
setInput(input, null)
|
|
||||||
var isItem = false
|
|
||||||
while (next() != XmlPullParser.END_DOCUMENT) {
|
|
||||||
when (eventType) {
|
|
||||||
XmlPullParser.START_TAG -> if (name == "item") isItem = true
|
|
||||||
XmlPullParser.TEXT -> if (isItem) repoItems.add(text)
|
|
||||||
XmlPullParser.END_TAG -> isItem = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repoItems.chunked(7).mapNotNull { itemsSet -> fromXML(itemsSet) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fromXML(xml: List<String>) = runCatching {
|
|
||||||
defaultRepository(
|
|
||||||
name = xml[0],
|
|
||||||
address = xml[1],
|
|
||||||
description = xml[2].replace(Regex("\\s+"), " ").trim(),
|
|
||||||
version = xml[3].toInt(),
|
|
||||||
enabled = xml[4].toInt() > 0,
|
|
||||||
fingerprint = xml[6].let {
|
|
||||||
if (it.length > 32) it.fingerprint().value
|
|
||||||
else it
|
|
||||||
},
|
|
||||||
authentication = "",
|
|
||||||
)
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.looker.droidify.installer.installers
|
|
||||||
|
|
||||||
import com.looker.droidify.domain.model.PackageName
|
|
||||||
import com.looker.droidify.installer.model.InstallItem
|
|
||||||
import com.looker.droidify.installer.model.InstallState
|
|
||||||
|
|
||||||
interface Installer : AutoCloseable {
|
|
||||||
|
|
||||||
suspend fun install(installItem: InstallItem): InstallState
|
|
||||||
|
|
||||||
suspend fun uninstall(packageName: PackageName)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package com.looker.droidify.installer.installers
|
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.AndroidRuntimeException
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.looker.droidify.R
|
|
||||||
import com.looker.droidify.datastore.SettingsRepository
|
|
||||||
import com.looker.droidify.datastore.get
|
|
||||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
|
||||||
import com.looker.droidify.domain.model.PackageName
|
|
||||||
import com.looker.droidify.installer.model.InstallItem
|
|
||||||
import com.looker.droidify.installer.model.InstallState
|
|
||||||
import com.looker.droidify.utility.common.SdkCheck
|
|
||||||
import com.looker.droidify.utility.common.cache.Cache
|
|
||||||
import com.looker.droidify.utility.common.extension.intent
|
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
class LegacyInstaller(
|
|
||||||
private val context: Context,
|
|
||||||
private val settingsRepository: SettingsRepository
|
|
||||||
) : Installer {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val APK_MIME = "application/vnd.android.package-archive"
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun install(
|
|
||||||
installItem: InstallItem,
|
|
||||||
): InstallState {
|
|
||||||
val installFlag = if (SdkCheck.isNougat) Intent.FLAG_GRANT_READ_URI_PERMISSION else 0
|
|
||||||
val fileUri = if (SdkCheck.isNougat) {
|
|
||||||
Cache.getReleaseUri(context, installItem.installFileName)
|
|
||||||
} else {
|
|
||||||
Cache.getReleaseFile(context, installItem.installFileName).toUri()
|
|
||||||
}
|
|
||||||
|
|
||||||
val comp = settingsRepository.get { legacyInstallerComponent }.firstOrNull()
|
|
||||||
|
|
||||||
return suspendCancellableCoroutine { cont ->
|
|
||||||
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
|
|
||||||
setDataAndType(fileUri, APK_MIME)
|
|
||||||
flags = installFlag
|
|
||||||
when (comp) {
|
|
||||||
is LegacyInstallerComponent.Component -> {
|
|
||||||
component = ComponentName(comp.clazz, comp.activity)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// For Unspecified and AlwaysChoose, don't set component
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val installIntent = when (comp) {
|
|
||||||
LegacyInstallerComponent.AlwaysChoose -> Intent.createChooser(intent, context.getString(
|
|
||||||
R.string.select_installer))
|
|
||||||
else -> intent
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
context.startActivity(installIntent)
|
|
||||||
cont.resume(InstallState.Installed)
|
|
||||||
} catch (e: AndroidRuntimeException) {
|
|
||||||
installIntent.flags = installFlag or Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
try {
|
|
||||||
context.startActivity(installIntent)
|
|
||||||
cont.resume(InstallState.Installed)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
cont.resume(InstallState.Failed)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
cont.resume(InstallState.Failed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun uninstall(packageName: PackageName) =
|
|
||||||
context.uninstallPackage(packageName)
|
|
||||||
|
|
||||||
override fun close() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun Context.uninstallPackage(packageName: PackageName) =
|
|
||||||
suspendCancellableCoroutine { cont ->
|
|
||||||
try {
|
|
||||||
startActivity(
|
|
||||||
intent(Intent.ACTION_UNINSTALL_PACKAGE) {
|
|
||||||
data = "package:${packageName.name}".toUri()
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
}
|
|
||||||
)
|
|
||||||
cont.resume(Unit)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
cont.resume(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,410 +0,0 @@
|
|||||||
package com.looker.droidify.model
|
|
||||||
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
data class Repository(
|
|
||||||
var id: Long,
|
|
||||||
val address: String,
|
|
||||||
val mirrors: List<String>,
|
|
||||||
val name: String,
|
|
||||||
val description: String,
|
|
||||||
val version: Int,
|
|
||||||
val enabled: Boolean,
|
|
||||||
val fingerprint: String,
|
|
||||||
val lastModified: String,
|
|
||||||
val entityTag: String,
|
|
||||||
val updated: Long,
|
|
||||||
val timestamp: Long,
|
|
||||||
val authentication: String,
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun edit(address: String, fingerprint: String, authentication: String): Repository {
|
|
||||||
val isAddressChanged = this.address != address
|
|
||||||
val isFingerprintChanged = this.fingerprint != fingerprint
|
|
||||||
val shouldForceUpdate = isAddressChanged || isFingerprintChanged
|
|
||||||
return copy(
|
|
||||||
address = address,
|
|
||||||
fingerprint = fingerprint,
|
|
||||||
lastModified = if (shouldForceUpdate) "" else lastModified,
|
|
||||||
entityTag = if (shouldForceUpdate) "" else entityTag,
|
|
||||||
authentication = authentication
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(
|
|
||||||
mirrors: List<String>,
|
|
||||||
name: String,
|
|
||||||
description: String,
|
|
||||||
version: Int,
|
|
||||||
lastModified: String,
|
|
||||||
entityTag: String,
|
|
||||||
timestamp: Long,
|
|
||||||
): Repository {
|
|
||||||
return copy(
|
|
||||||
mirrors = mirrors,
|
|
||||||
name = name,
|
|
||||||
description = description,
|
|
||||||
version = if (version >= 0) version else this.version,
|
|
||||||
lastModified = lastModified,
|
|
||||||
entityTag = entityTag,
|
|
||||||
updated = System.currentTimeMillis(),
|
|
||||||
timestamp = timestamp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enable(enabled: Boolean): Repository {
|
|
||||||
return copy(enabled = enabled, lastModified = "", entityTag = "")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("SpellCheckingInspection")
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun newRepository(
|
|
||||||
address: String,
|
|
||||||
fingerprint: String,
|
|
||||||
authentication: String,
|
|
||||||
): Repository {
|
|
||||||
val name = try {
|
|
||||||
URL(address).let { "${it.host}${it.path}" }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
address
|
|
||||||
}
|
|
||||||
return defaultRepository(address, name, "", 0, true, fingerprint, authentication)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun defaultRepository(
|
|
||||||
address: String,
|
|
||||||
name: String,
|
|
||||||
description: String,
|
|
||||||
version: Int = 21,
|
|
||||||
enabled: Boolean = false,
|
|
||||||
fingerprint: String,
|
|
||||||
authentication: String = "",
|
|
||||||
): Repository {
|
|
||||||
return Repository(
|
|
||||||
-1, address, emptyList(), name, description, version, enabled,
|
|
||||||
fingerprint, "", "", 0L, 0L, authentication
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val defaultRepositories = listOf(
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://f-droid.org/repo",
|
|
||||||
name = "F-Droid",
|
|
||||||
description = "The official F-Droid Free Software repos" +
|
|
||||||
"itory. Everything in this repository is always buil" +
|
|
||||||
"t from the source code.",
|
|
||||||
enabled = true,
|
|
||||||
fingerprint = "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://f-droid.org/archive",
|
|
||||||
name = "F-Droid Archive",
|
|
||||||
description = "The archive of the official F-Droid Free" +
|
|
||||||
" Software repository. Apps here are old and can co" +
|
|
||||||
"ntain known vulnerabilities and security issues!",
|
|
||||||
fingerprint = "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://guardianproject.info/fdroid/repo",
|
|
||||||
name = "Guardian Project Official Releases",
|
|
||||||
description = "The official repository of The Guardian " +
|
|
||||||
"Project apps for use with the F-Droid client. Appl" +
|
|
||||||
"ications in this repository are official binaries " +
|
|
||||||
"built by the original application developers and " +
|
|
||||||
"signed by the same key as the APKs that are relea" +
|
|
||||||
"sed in the Google Play Store.",
|
|
||||||
fingerprint = "B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A6135"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://guardianproject.info/fdroid/archive",
|
|
||||||
name = "Guardian Project Archive",
|
|
||||||
description = "The official repository of The Guardian Pr" +
|
|
||||||
"oject apps for use with the F-Droid client. This con" +
|
|
||||||
"tains older versions of applications from the main repository.",
|
|
||||||
fingerprint = "B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A6135"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://apt.izzysoft.de/fdroid/repo",
|
|
||||||
name = "IzzyOnDroid F-Droid Repo",
|
|
||||||
description = "This is a repository of apps to be used with" +
|
|
||||||
" F-Droid the original application developers, taken" +
|
|
||||||
" from the resp. repositories (mostly GitHub). At thi" +
|
|
||||||
"s moment I cannot give guarantees on regular updates" +
|
|
||||||
" for all of them, though most are checked multiple times a week ",
|
|
||||||
enabled = true,
|
|
||||||
fingerprint = "3BF0D6ABFEAE2F401707B6D966BE743BF0EEE49C2561B9BA39073711F628937A"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://microg.org/fdroid/repo",
|
|
||||||
name = "microG Project",
|
|
||||||
description = "The official repository for microG." +
|
|
||||||
" microG is a lightweight open source implementation" +
|
|
||||||
" of Google Play Services.",
|
|
||||||
fingerprint = "9BD06727E62796C0130EB6DAB39B73157451582CBD138E86C468ACC395D14165"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://repo.netsyms.com/fdroid/repo",
|
|
||||||
name = "Netsyms Technologies",
|
|
||||||
description = "Official collection of open-source apps created" +
|
|
||||||
" by Netsyms Technologies.",
|
|
||||||
fingerprint = "2581BA7B32D3AB443180C4087CAB6A7E8FB258D3A6E98870ECB3C675E4D64489"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://molly.im/fdroid/foss/fdroid/repo",
|
|
||||||
name = "Molly",
|
|
||||||
description = "The official repository for Molly. " +
|
|
||||||
"Molly is a fork of Signal focused on security.",
|
|
||||||
fingerprint = "5198DAEF37FC23C14D5EE32305B2AF45787BD7DF2034DE33AD302BDB3446DF74"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://archive.newpipe.net/fdroid/repo",
|
|
||||||
name = "NewPipe",
|
|
||||||
description = "The official repository for NewPipe." +
|
|
||||||
" NewPipe is a lightweight client for Youtube, PeerTube" +
|
|
||||||
", Soundcloud, etc.",
|
|
||||||
fingerprint = "E2402C78F9B97C6C89E97DB914A2751FDA1D02FE2039CC0897A462BDB57E7501"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://www.collaboraoffice.com/downloads/fdroid/repo",
|
|
||||||
name = "Collabora Office",
|
|
||||||
description = "Collabora Office is an office suite based on LibreOffice.",
|
|
||||||
fingerprint = "573258C84E149B5F4D9299E7434B2B69A8410372921D4AE586BA91EC767892CC"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://cdn.kde.org/android/fdroid/repo",
|
|
||||||
name = "KDE Android",
|
|
||||||
description = "The official nightly repository for KDE Android apps.",
|
|
||||||
fingerprint = "B3EBE10AFA6C5C400379B34473E843D686C61AE6AD33F423C98AF903F056523F"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://calyxos.gitlab.io/calyx-fdroid-repo/fdroid/repo",
|
|
||||||
name = "Calyx OS Repo",
|
|
||||||
description = "The official Calyx Labs F-Droid repository.",
|
|
||||||
fingerprint = "C44D58B4547DE5096138CB0B34A1CC99DAB3B4274412ED753FCCBFC11DC1B7B6"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://divestos.org/fdroid/official",
|
|
||||||
name = "Divest OS Repo",
|
|
||||||
description = "The official Divest OS F-Droid repository.",
|
|
||||||
fingerprint = "E4BE8D6ABFA4D9D4FEEF03CDDA7FF62A73FD64B75566F6DD4E5E577550BE8467"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.fedilab.app/repo",
|
|
||||||
name = "Fedilab",
|
|
||||||
description = "The official repository for Fedilab. Fedilab is a " +
|
|
||||||
"multi-accounts client for Mastodon, PeerTube, and other free" +
|
|
||||||
" software social networks.",
|
|
||||||
fingerprint = "11F0A69910A4280E2CD3CCC3146337D006BE539B18E1A9FEACE15FF757A94FEB"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://store.nethunter.com/repo",
|
|
||||||
name = "Kali Nethunter",
|
|
||||||
description = "Kali Nethunter's official selection of original b" +
|
|
||||||
"inaries.",
|
|
||||||
fingerprint = "FE7A23DFC003A1CF2D2ADD2469B9C0C49B206BA5DC9EDD6563B3B7EB6A8F5FAB"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://thecapslock.gitlab.io/fdroid-patched-apps/fdroid/repo",
|
|
||||||
name = "Patched Apps",
|
|
||||||
description = "A collection of patched applications to provid" +
|
|
||||||
"e better compatibility, privacy etc..",
|
|
||||||
fingerprint = "313D9E6E789FF4E8E2D687AAE31EEF576050003ED67963301821AC6D3763E3AC"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://mobileapp.bitwarden.com/fdroid/repo",
|
|
||||||
name = "Bitwarden",
|
|
||||||
description = "The official repository for Bitwarden. Bitward" +
|
|
||||||
"en is a password manager.",
|
|
||||||
fingerprint = "BC54EA6FD1CD5175BCCCC47C561C5726E1C3ED7E686B6DB4B18BAC843A3EFE6C"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://briarproject.org/fdroid/repo",
|
|
||||||
name = "Briar",
|
|
||||||
description = "The official repository for Briar. Briar is a" +
|
|
||||||
" serverless/offline messenger that focused on privacy, s" +
|
|
||||||
"ecurity, and decentralization.",
|
|
||||||
fingerprint = "1FB874BEE7276D28ECB2C9B06E8A122EC4BCB4008161436CE474C257CBF49BD6"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://guardianproject-wind.s3.amazonaws.com/fdroid/repo",
|
|
||||||
name = "Wind Project",
|
|
||||||
description = "A collection of interesting offline/serverless apps.",
|
|
||||||
fingerprint = "182CF464D219D340DA443C62155198E399FEC1BC4379309B775DD9FC97ED97E1"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://nanolx.org/fdroid/repo",
|
|
||||||
name = "NanoDroid",
|
|
||||||
description = "A companion repository to microG's installer.",
|
|
||||||
fingerprint = "862ED9F13A3981432BF86FE93D14596B381D75BE83A1D616E2D44A12654AD015"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://releases.threema.ch/fdroid/repo",
|
|
||||||
name = "Threema Libre",
|
|
||||||
description = "The official repository for Threema Libre. R" +
|
|
||||||
"equires Threema Shop license. Threema Libre is an open" +
|
|
||||||
"-source messenger focused on security and privacy.",
|
|
||||||
fingerprint = "5734E753899B25775D90FE85362A49866E05AC4F83C05BEF5A92880D2910639E"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.getsession.org/fdroid/repo",
|
|
||||||
name = "Session",
|
|
||||||
description = "The official repository for Session. Session" +
|
|
||||||
" is an open-source messenger focused on security and privacy.",
|
|
||||||
fingerprint = "DB0E5297EB65CC22D6BD93C869943BDCFCB6A07DC69A48A0DD8C7BA698EC04E6"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://www.cromite.org/fdroid/repo",
|
|
||||||
name = "Cromite",
|
|
||||||
description = "The official repository for Cromite. Cromite" +
|
|
||||||
" is a Chromium with ad blocking and enhanced privacy.",
|
|
||||||
fingerprint = "49F37E74DEE483DCA2B991334FB5A0200787430D0B5F9A783DD5F13695E9517B"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.twinhelix.com/fdroid/repo",
|
|
||||||
name = "TwinHelix",
|
|
||||||
description = "TwinHelix F-Droid Repository, used for Signa" +
|
|
||||||
"l-FOSS, an open-source fork of Signal Private Messenger.",
|
|
||||||
fingerprint = "7b03b0232209b21b10a30a63897d3c6bca4f58fe29bc3477e8e3d8cf8e304028"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.typeblog.net",
|
|
||||||
name = "PeterCxy's F-Droid",
|
|
||||||
description = "You have landed on PeterCxy's F-Droid repo. T" +
|
|
||||||
"o use this repository, please add the page's URL to your F-Droid client.",
|
|
||||||
fingerprint = "1a7e446c491c80bc2f83844a26387887990f97f2f379ae7b109679feae3dbc8c"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://s2.spiritcroc.de/fdroid/repo",
|
|
||||||
name = "SpiritCroc.de",
|
|
||||||
description = "While some of my apps are available from" +
|
|
||||||
" the official F-Droid repository, I also maintain my" +
|
|
||||||
" own repository for a small selection of apps. These" +
|
|
||||||
" might be forks of other apps with only minor change" +
|
|
||||||
"s, or apps that are not published on the Play Store f" +
|
|
||||||
"or other reasons. In contrast to the official F-Droid" +
|
|
||||||
" repos, these might also include proprietary librarie" +
|
|
||||||
"s, e.g. for push notifications.",
|
|
||||||
fingerprint = "6612ade7e93174a589cf5ba26ed3ab28231a789640546c8f30375ef045bc9242"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://s2.spiritcroc.de/testing/fdroid/repo",
|
|
||||||
name = "SpiritCroc.de Test Builds",
|
|
||||||
description = "SpiritCroc.de Test Builds",
|
|
||||||
fingerprint = "52d03f2fab785573bb295c7ab270695e3a1bdd2adc6a6de8713250b33f231225"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://static.cryptomator.org/android/fdroid/repo",
|
|
||||||
name = "Cryptomator",
|
|
||||||
description = "No Description",
|
|
||||||
fingerprint = "f7c3ec3b0d588d3cb52983e9eb1a7421c93d4339a286398e71d7b651e8d8ecdd"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://divestos.org/apks/unofficial/fdroid/repo",
|
|
||||||
name = "DivestOS Unofficial",
|
|
||||||
description = "This repository contains unofficial builds of open source apps" +
|
|
||||||
" that are not included in the other repos.",
|
|
||||||
fingerprint = "a18cdb92f40ebfbbf778a54fd12dbd74d90f1490cb9ef2cc6c7e682dd556855d"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://cdn.kde.org/android/stable-releases/fdroid/repo",
|
|
||||||
name = "KDE Stables",
|
|
||||||
description = "This repository contains unofficial builds of open source apps" +
|
|
||||||
" that are not included in the other repos.",
|
|
||||||
fingerprint = "13784ba6c80ff4e2181e55c56f961eed5844cea16870d3b38d58780b85e1158f"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://zimbelstern.eu/fdroid/repo",
|
|
||||||
name = "Zimbelstern's F-Droid repository",
|
|
||||||
description = "This is the official repository of apps from zimbelstern.eu," +
|
|
||||||
" to be used with F-Droid.",
|
|
||||||
fingerprint = "285158DECEF37CB8DE7C5AF14818ACBF4A9B1FBE63116758EFC267F971CA23AA"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://app.simplex.chat/fdroid/repo",
|
|
||||||
name = "SimpleX Chat F-Droid",
|
|
||||||
description = "SimpleX Chat official F-Droid repository.",
|
|
||||||
fingerprint = "9F358FF284D1F71656A2BFAF0E005DEAE6AA14143720E089F11FF2DDCFEB01BA"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://f-droid.monerujo.io/fdroid/repo",
|
|
||||||
name = "Monerujo Wallet",
|
|
||||||
description = "Monerujo Monero Wallet official F-Droid repository.",
|
|
||||||
fingerprint = "A82C68E14AF0AA6A2EC20E6B272EFF25E5A038F3F65884316E0F5E0D91E7B713"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.cakelabs.com/fdroid/repo",
|
|
||||||
name = "Cake Labs",
|
|
||||||
description = "Cake Labs official F-Droid repository for Cake Wallet and Monero.com",
|
|
||||||
fingerprint = "EA44EFAEE0B641EE7A032D397D5D976F9C4E5E1ED26E11C75702D064E55F8755"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://app.futo.org/fdroid/repo",
|
|
||||||
name = "FUTO",
|
|
||||||
description = "FUTO official F-Droid repository.",
|
|
||||||
fingerprint = "39D47869D29CBFCE4691D9F7E6946A7B6D7E6FF4883497E6E675744ECDFA6D6D"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.mm20.de/repo",
|
|
||||||
name = "MM20 Apps",
|
|
||||||
description = "Apps developed and distributed by MM20",
|
|
||||||
fingerprint = "156FBAB952F6996415F198F3F29628D24B30E725B0F07A2B49C3A9B5161EEE1A"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://breezy-weather.github.io/fdroid-repo/fdroid/repo",
|
|
||||||
name = "Breezy Weather",
|
|
||||||
description = "The F-Droid repository for Breezy Weather",
|
|
||||||
fingerprint = "3480A7BB2A296D8F98CB90D2309199B5B9519C1B31978DBCD877ADB102AF35EE"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://gh.artemchep.com/keyguard-repo-fdroid/repo",
|
|
||||||
name = "Keyguard Project",
|
|
||||||
description = "Mirrors artifacts available on https://github.com/AChep/keyguard-app/releases",
|
|
||||||
fingerprint = "03941CE79B081666609C8A48AB6E46774263F6FC0BBF1FA046CCFFC60EA643BC"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://f5a.torus.icu/fdroid/repo",
|
|
||||||
name = "Fcitx 5 For Android F-Droid Repo",
|
|
||||||
description = "Out-of-tree fcitx5-android plugins.",
|
|
||||||
fingerprint = "5D87CE1FAD3772425C2A7ED987A57595A20B07543B9595A7FD2CED25DFF3CF12"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.i2pd.xyz/fdroid/repo/",
|
|
||||||
name = "PurpleI2P F-Droid repository",
|
|
||||||
description = "This is a repository of PurpleI2P. It contains applications developed and supported by our team.",
|
|
||||||
fingerprint = "5D87CE1FAD3772425C2A7ED987A57595A20B07543B9595A7FD2CED25DFF3CF12"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val newlyAdded: List<Repository> = listOf(
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://fdroid.ironfoxoss.org/fdroid/repo",
|
|
||||||
name = "IronFox",
|
|
||||||
description = "The official repository for IronFox:" +
|
|
||||||
" A privacy and security-oriented Firefox-based browser for Android.",
|
|
||||||
fingerprint = "C5E291B5A571F9C8CD9A9799C2C94E02EC9703948893F2CA756D67B94204F904"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://raw.githubusercontent.com/chrisgch/tca/master/fdroid/repo",
|
|
||||||
name = "Total Commander",
|
|
||||||
description = "The official repository for Total Commander",
|
|
||||||
fingerprint = "3576596CECDD70488D61CFD90799A49B7FFD26A81A8FEF1BADEC88D069FA72C1"
|
|
||||||
),
|
|
||||||
defaultRepository(
|
|
||||||
address = "https://www.cromite.org/fdroid/repo",
|
|
||||||
name = "Cromite",
|
|
||||||
description = "The official repository for Cromite. " +
|
|
||||||
"Cromite is a Chromium fork based on Bromite with " +
|
|
||||||
"built-in support for ad blocking and an eye for privacy.",
|
|
||||||
fingerprint = "49F37E74DEE483DCA2B991334FB5A0200787430D0B5F9A783DD5F13695E9517B"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val toRemove: List<String> = listOf(
|
|
||||||
// Add repository addresses that should be removed during database upgrades and remove them from the lists above
|
|
||||||
// Example: "https://example.com/fdroid/repo"
|
|
||||||
"https://secfirst.org/fdroid/repo",
|
|
||||||
"https://fdroid.libretro.com/repo"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.looker.droidify.sync
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
|
||||||
import com.looker.droidify.domain.model.Repo
|
|
||||||
import com.looker.droidify.sync.common.JsonParser
|
|
||||||
import com.looker.droidify.sync.v2.V2Parser
|
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
|
||||||
import com.looker.droidify.utility.common.cache.Cache
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
|
|
||||||
class LocalSyncable(
|
|
||||||
private val context: Context,
|
|
||||||
) : Syncable<IndexV2> {
|
|
||||||
override val parser: Parser<IndexV2>
|
|
||||||
get() = V2Parser(Dispatchers.IO, JsonParser)
|
|
||||||
|
|
||||||
override suspend fun sync(repo: Repo): Pair<Fingerprint, IndexV2?> {
|
|
||||||
val file = Cache.getTemporaryFile(context).apply {
|
|
||||||
outputStream().use {
|
|
||||||
it.write(context.assets.open("izzy_index_v2.json").readBytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parser.parse(file, repo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.looker.droidify.sync
|
|
||||||
|
|
||||||
import com.looker.droidify.domain.model.Fingerprint
|
|
||||||
import com.looker.droidify.domain.model.Repo
|
|
||||||
import com.looker.droidify.sync.v2.model.IndexV2
|
|
||||||
|
|
||||||
interface Syncable<T> {
|
|
||||||
|
|
||||||
val parser: Parser<T>
|
|
||||||
|
|
||||||
suspend fun sync(
|
|
||||||
repo: Repo,
|
|
||||||
): Pair<Fingerprint, IndexV2?>
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package com.looker.droidify.sync.v2.model
|
|
||||||
|
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
|
|
||||||
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>>
|
|
||||||
|
|
||||||
typealias DefaultName = String
|
|
||||||
typealias Tag = String
|
|
||||||
|
|
||||||
typealias AntiFeatureReason = LocalizedString
|
|
||||||
|
|
||||||
fun Map<String, Any>?.localesSize(): Int? = this?.keys?.size
|
|
||||||
|
|
||||||
fun Map<String, Any>?.locales(): List<String> = buildList {
|
|
||||||
if (!isNullOrEmpty()) {
|
|
||||||
for (locale in this@locales!!.keys) {
|
|
||||||
add(locale)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> Map<String, T>?.localizedValue(locale: String): T? {
|
|
||||||
if (isNullOrEmpty()) return null
|
|
||||||
val localeList = LocaleListCompat.forLanguageTags(locale)
|
|
||||||
val match = localeList.getFirstMatch(keys.toTypedArray()) ?: return null
|
|
||||||
return get(match.toLanguageTag()) ?: run {
|
|
||||||
val langCountryTag = "${match.language}-${match.country}"
|
|
||||||
getOrStartsWith(langCountryTag) ?: run {
|
|
||||||
val langTag = match.language
|
|
||||||
getOrStartsWith(langTag) ?: get("en-US") ?: get("en") ?: values.first()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> Map<String, T>.getOrStartsWith(s: String): T? = get(s) ?: run {
|
|
||||||
entries.forEach { (key, value) ->
|
|
||||||
if (key.startsWith(s)) return value
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.looker.droidify.utility.extension
|
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import com.looker.droidify.utility.common.extension.calculateHash
|
|
||||||
import com.looker.droidify.utility.common.extension.singleSignature
|
|
||||||
import com.looker.droidify.utility.common.extension.versionCodeCompat
|
|
||||||
import com.looker.droidify.model.InstalledItem
|
|
||||||
|
|
||||||
fun PackageInfo.toInstalledItem(): InstalledItem {
|
|
||||||
val signatureString = singleSignature?.calculateHash().orEmpty()
|
|
||||||
return InstalledItem(
|
|
||||||
packageName,
|
|
||||||
versionName.orEmpty(),
|
|
||||||
versionCodeCompat,
|
|
||||||
signatureString
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify
|
package de.felitendo.felostore
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
@@ -21,30 +21,30 @@ import coil3.disk.DiskCache
|
|||||||
import coil3.disk.directory
|
import coil3.disk.directory
|
||||||
import coil3.memory.MemoryCache
|
import coil3.memory.MemoryCache
|
||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
import com.looker.droidify.content.ProductPreferences
|
import de.felitendo.felostore.content.ProductPreferences
|
||||||
import com.looker.droidify.database.Database
|
import de.felitendo.felostore.database.Database
|
||||||
import com.looker.droidify.datastore.SettingsRepository
|
import de.felitendo.felostore.datastore.SettingsRepository
|
||||||
import com.looker.droidify.datastore.get
|
import de.felitendo.felostore.datastore.get
|
||||||
import com.looker.droidify.datastore.model.AutoSync
|
import de.felitendo.felostore.datastore.model.AutoSync
|
||||||
import com.looker.droidify.datastore.model.ProxyPreference
|
import de.felitendo.felostore.datastore.model.ProxyPreference
|
||||||
import com.looker.droidify.datastore.model.ProxyType
|
import de.felitendo.felostore.datastore.model.ProxyType
|
||||||
import com.looker.droidify.index.RepositoryUpdater
|
import de.felitendo.felostore.index.RepositoryUpdater
|
||||||
import com.looker.droidify.installer.InstallManager
|
import de.felitendo.felostore.installer.InstallManager
|
||||||
import com.looker.droidify.network.Downloader
|
import de.felitendo.felostore.network.Downloader
|
||||||
import com.looker.droidify.receivers.InstalledAppReceiver
|
import de.felitendo.felostore.receivers.InstalledAppReceiver
|
||||||
import com.looker.droidify.service.Connection
|
import de.felitendo.felostore.service.Connection
|
||||||
import com.looker.droidify.service.SyncService
|
import de.felitendo.felostore.service.SyncService
|
||||||
import com.looker.droidify.sync.SyncPreference
|
import de.felitendo.felostore.sync.SyncPreference
|
||||||
import com.looker.droidify.sync.toJobNetworkType
|
import de.felitendo.felostore.sync.toJobNetworkType
|
||||||
import com.looker.droidify.utility.common.Constants
|
import de.felitendo.felostore.utility.common.Constants
|
||||||
import com.looker.droidify.utility.common.SdkCheck
|
import de.felitendo.felostore.utility.common.SdkCheck
|
||||||
import com.looker.droidify.utility.common.cache.Cache
|
import de.felitendo.felostore.utility.common.cache.Cache
|
||||||
import com.looker.droidify.utility.common.extension.getDrawableCompat
|
import de.felitendo.felostore.utility.common.extension.getDrawableCompat
|
||||||
import com.looker.droidify.utility.common.extension.getInstalledPackagesCompat
|
import de.felitendo.felostore.utility.common.extension.getInstalledPackagesCompat
|
||||||
import com.looker.droidify.utility.common.extension.jobScheduler
|
import de.felitendo.felostore.utility.common.extension.jobScheduler
|
||||||
import com.looker.droidify.utility.common.log
|
import de.felitendo.felostore.utility.common.log
|
||||||
import com.looker.droidify.utility.extension.toInstalledItem
|
import de.felitendo.felostore.utility.extension.toInstalledItem
|
||||||
import com.looker.droidify.work.CleanUpWorker
|
import de.felitendo.felostore.work.CleanUpWorker
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -60,7 +60,7 @@ import kotlin.time.Duration.Companion.INFINITE
|
|||||||
import kotlin.time.Duration.Companion.hours
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Provider {
|
class FeloStore : Application(), SingletonImageLoader.Factory, Configuration.Provider {
|
||||||
|
|
||||||
private val parentJob = SupervisorJob()
|
private val parentJob = SupervisorJob()
|
||||||
private val appScope = CoroutineScope(Dispatchers.Default + parentJob)
|
private val appScope = CoroutineScope(Dispatchers.Default + parentJob)
|
||||||
@@ -80,7 +80,7 @@ class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Prov
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
// if (BuildConfig.DEBUG && SdkCheck.isOreo) strictThreadPolicy()
|
if (BuildConfig.DEBUG && SdkCheck.isOreo) strictThreadPolicy()
|
||||||
|
|
||||||
val databaseUpdated = Database.init(this)
|
val databaseUpdated = Database.init(this)
|
||||||
ProductPreferences.init(this, appScope)
|
ProductPreferences.init(this, appScope)
|
||||||
@@ -107,7 +107,7 @@ class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Prov
|
|||||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||||
addDataScheme("package")
|
addDataScheme("package")
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
val installedItems =
|
val installedItems =
|
||||||
packageManager.getInstalledPackagesCompat()
|
packageManager.getInstalledPackagesCompat()
|
||||||
@@ -200,7 +200,7 @@ class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Prov
|
|||||||
periodMillis = period,
|
periodMillis = period,
|
||||||
networkType = syncConditions.toJobNetworkType(),
|
networkType = syncConditions.toJobNetworkType(),
|
||||||
isCharging = syncConditions.pluggedIn,
|
isCharging = syncConditions.pluggedIn,
|
||||||
isBatteryLow = syncConditions.batteryNotLow,
|
isBatteryLow = syncConditions.batteryNotLow
|
||||||
)
|
)
|
||||||
jobScheduler?.schedule(job)
|
jobScheduler?.schedule(job)
|
||||||
}
|
}
|
||||||
@@ -212,13 +212,10 @@ class Droidify : Application(), SingletonImageLoader.Factory, Configuration.Prov
|
|||||||
Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = ""))
|
Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Connection(
|
Connection(SyncService::class.java, onBind = { connection, binder ->
|
||||||
SyncService::class.java,
|
binder.sync(SyncService.SyncRequest.FORCE)
|
||||||
onBind = { connection, binder ->
|
connection.unbind(this)
|
||||||
binder.sync(SyncService.SyncRequest.FORCE)
|
}).bind(this)
|
||||||
connection.unbind(this)
|
|
||||||
},
|
|
||||||
).bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BootReceiver : BroadcastReceiver() {
|
class BootReceiver : BroadcastReceiver() {
|
||||||
@@ -259,12 +256,12 @@ fun strictThreadPolicy() {
|
|||||||
.detectNetwork()
|
.detectNetwork()
|
||||||
.detectUnbufferedIo()
|
.detectUnbufferedIo()
|
||||||
.penaltyLog()
|
.penaltyLog()
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
StrictMode.setVmPolicy(
|
StrictMode.setVmPolicy(
|
||||||
StrictMode.VmPolicy.Builder()
|
StrictMode.VmPolicy.Builder()
|
||||||
.detectAll()
|
.detectAll()
|
||||||
.penaltyLog()
|
.penaltyLog()
|
||||||
.build(),
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify
|
package de.felitendo.felostore
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -14,26 +14,26 @@ import androidx.core.view.WindowCompat
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.looker.droidify.database.CursorOwner
|
import de.felitendo.felostore.utility.common.DeeplinkType
|
||||||
import com.looker.droidify.datastore.SettingsRepository
|
import de.felitendo.felostore.utility.common.SdkCheck
|
||||||
import com.looker.droidify.datastore.extension.getThemeRes
|
import de.felitendo.felostore.utility.common.deeplinkType
|
||||||
import com.looker.droidify.datastore.get
|
import de.felitendo.felostore.utility.common.extension.homeAsUp
|
||||||
import com.looker.droidify.installer.InstallManager
|
import de.felitendo.felostore.utility.common.extension.inputManager
|
||||||
import com.looker.droidify.installer.model.installFrom
|
import de.felitendo.felostore.utility.common.getInstallPackageName
|
||||||
import com.looker.droidify.ui.appDetail.AppDetailFragment
|
import de.felitendo.felostore.utility.common.requestNotificationPermission
|
||||||
import com.looker.droidify.ui.favourites.FavouritesFragment
|
import de.felitendo.felostore.database.CursorOwner
|
||||||
import com.looker.droidify.ui.repository.EditRepositoryFragment
|
import de.felitendo.felostore.datastore.SettingsRepository
|
||||||
import com.looker.droidify.ui.repository.RepositoriesFragment
|
import de.felitendo.felostore.datastore.extension.getThemeRes
|
||||||
import com.looker.droidify.ui.repository.RepositoryFragment
|
import de.felitendo.felostore.datastore.get
|
||||||
import com.looker.droidify.ui.settings.SettingsFragment
|
import de.felitendo.felostore.installer.InstallManager
|
||||||
import com.looker.droidify.ui.tabsFragment.TabsFragment
|
import de.felitendo.felostore.installer.model.installFrom
|
||||||
import com.looker.droidify.utility.common.DeeplinkType
|
import de.felitendo.felostore.ui.appDetail.AppDetailFragment
|
||||||
import com.looker.droidify.utility.common.SdkCheck
|
import de.felitendo.felostore.ui.favourites.FavouritesFragment
|
||||||
import com.looker.droidify.utility.common.deeplinkType
|
import de.felitendo.felostore.ui.repository.EditRepositoryFragment
|
||||||
import com.looker.droidify.utility.common.extension.homeAsUp
|
import de.felitendo.felostore.ui.repository.RepositoriesFragment
|
||||||
import com.looker.droidify.utility.common.extension.inputManager
|
import de.felitendo.felostore.ui.repository.RepositoryFragment
|
||||||
import com.looker.droidify.utility.common.getInstallPackageName
|
import de.felitendo.felostore.ui.settings.SettingsFragment
|
||||||
import com.looker.droidify.utility.common.requestNotificationPermission
|
import de.felitendo.felostore.ui.tabsFragment.TabsFragment
|
||||||
import dagger.hilt.EntryPoint
|
import dagger.hilt.EntryPoint
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -64,7 +64,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
private class FragmentStackItem(
|
private class FragmentStackItem(
|
||||||
val className: String, val arguments: Bundle?, val savedState: Fragment.SavedState?,
|
val className: String, val arguments: Bundle?, val savedState: Fragment.SavedState?
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
lateinit var cursorOwner: CursorOwner
|
lateinit var cursorOwner: CursorOwner
|
||||||
@@ -87,25 +87,24 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun collectChange() {
|
private fun collectChange() {
|
||||||
val hiltEntryPoint =
|
val hiltEntryPoint = EntryPointAccessors.fromApplication(
|
||||||
EntryPointAccessors.fromApplication(this, CustomUserRepositoryInjector::class.java)
|
this, CustomUserRepositoryInjector::class.java
|
||||||
|
)
|
||||||
val newSettings = hiltEntryPoint.settingsRepository().get { theme to dynamicTheme }
|
val newSettings = hiltEntryPoint.settingsRepository().get { theme to dynamicTheme }
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val theme = newSettings.first()
|
val theme = newSettings.first()
|
||||||
setTheme(
|
setTheme(
|
||||||
resources.configuration.getThemeRes(
|
resources.configuration.getThemeRes(
|
||||||
theme = theme.first,
|
theme = theme.first, dynamicTheme = theme.second
|
||||||
dynamicTheme = theme.second,
|
)
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
newSettings.drop(1).collect { themeAndDynamic ->
|
newSettings.drop(1).collect { themeAndDynamic ->
|
||||||
setTheme(
|
setTheme(
|
||||||
resources.configuration.getThemeRes(
|
resources.configuration.getThemeRes(
|
||||||
theme = themeAndDynamic.first,
|
theme = themeAndDynamic.first, dynamicTheme = themeAndDynamic.second
|
||||||
dynamicTheme = themeAndDynamic.second,
|
)
|
||||||
),
|
|
||||||
)
|
)
|
||||||
recreate()
|
recreate()
|
||||||
}
|
}
|
||||||
@@ -117,11 +116,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val rootView = FrameLayout(this).apply { id = R.id.main_content }
|
val rootView = FrameLayout(this).apply { id = R.id.main_content }
|
||||||
addContentView(
|
addContentView(
|
||||||
rootView,
|
rootView, ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams(
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
)
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
requestNotificationPermission(request = notificationPermission::launch)
|
requestNotificationPermission(request = notificationPermission::launch)
|
||||||
@@ -191,7 +188,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (open != null) {
|
if (open != null) {
|
||||||
setCustomAnimations(
|
setCustomAnimations(
|
||||||
if (open) R.animator.slide_in else 0,
|
if (open) R.animator.slide_in else 0,
|
||||||
if (open) R.animator.slide_in_keep else R.animator.slide_out,
|
if (open) R.animator.slide_in_keep else R.animator.slide_out
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
setReorderingAllowed(true)
|
setReorderingAllowed(true)
|
||||||
@@ -205,8 +202,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
FragmentStackItem(
|
FragmentStackItem(
|
||||||
it::class.java.name,
|
it::class.java.name,
|
||||||
it.arguments,
|
it.arguments,
|
||||||
supportFragmentManager.saveFragmentInstanceState(it),
|
supportFragmentManager.saveFragmentInstanceState(it)
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
replaceFragment(fragment, true)
|
replaceFragment(fragment, true)
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package com.looker.droidify.content
|
package de.felitendo.felostore.content
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import com.looker.droidify.utility.common.extension.Json
|
import de.felitendo.felostore.utility.common.extension.Json
|
||||||
import com.looker.droidify.utility.common.extension.parseDictionary
|
import de.felitendo.felostore.utility.common.extension.parseDictionary
|
||||||
import com.looker.droidify.utility.common.extension.writeDictionary
|
import de.felitendo.felostore.utility.common.extension.writeDictionary
|
||||||
import com.looker.droidify.model.ProductPreference
|
import de.felitendo.felostore.model.ProductPreference
|
||||||
import com.looker.droidify.database.Database
|
import de.felitendo.felostore.database.Database
|
||||||
import com.looker.droidify.utility.serialization.productPreference
|
import de.felitendo.felostore.utility.serialization.productPreference
|
||||||
import com.looker.droidify.utility.serialization.serialize
|
import de.felitendo.felostore.utility.serialization.serialize
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -1,49 +1,48 @@
|
|||||||
package com.looker.droidify.database
|
package de.felitendo.felostore.database
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.loader.app.LoaderManager
|
import androidx.loader.app.LoaderManager
|
||||||
import androidx.loader.content.Loader
|
import androidx.loader.content.Loader
|
||||||
import com.looker.droidify.datastore.SettingsRepository
|
import de.felitendo.felostore.datastore.model.SortOrder
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
import de.felitendo.felostore.model.ProductItem
|
||||||
import com.looker.droidify.model.ProductItem
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
sealed class Request {
|
||||||
@Inject
|
internal abstract val id: Int
|
||||||
lateinit var settingsRepository: SettingsRepository
|
|
||||||
|
|
||||||
sealed interface Request {
|
|
||||||
val id: Int
|
|
||||||
|
|
||||||
class Available(
|
class Available(
|
||||||
val searchQuery: String,
|
val searchQuery: String,
|
||||||
val section: ProductItem.Section,
|
val section: ProductItem.Section,
|
||||||
val order: SortOrder,
|
val order: SortOrder,
|
||||||
override val id: Int = 1,
|
) : Request() {
|
||||||
) : Request
|
override val id: Int
|
||||||
|
get() = 1
|
||||||
|
}
|
||||||
|
|
||||||
class Installed(
|
class Installed(
|
||||||
val searchQuery: String,
|
val searchQuery: String,
|
||||||
val section: ProductItem.Section,
|
val section: ProductItem.Section,
|
||||||
val order: SortOrder,
|
val order: SortOrder,
|
||||||
override val id: Int = 2,
|
) : Request() {
|
||||||
) : Request
|
override val id: Int
|
||||||
|
get() = 2
|
||||||
|
}
|
||||||
|
|
||||||
class Updates(
|
class Updates(
|
||||||
val searchQuery: String,
|
val searchQuery: String,
|
||||||
val section: ProductItem.Section,
|
val section: ProductItem.Section,
|
||||||
val order: SortOrder,
|
val order: SortOrder,
|
||||||
override val id: Int = 3,
|
val skipSignatureCheck: Boolean,
|
||||||
) : Request
|
) : Request() {
|
||||||
|
override val id: Int
|
||||||
|
get() = 3
|
||||||
|
}
|
||||||
|
|
||||||
object Repositories : Request {
|
object Repositories : Request() {
|
||||||
override val id = 4
|
override val id: Int
|
||||||
|
get() = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +56,10 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
val cursor: Cursor?,
|
val cursor: Cursor?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
retainInstance = true
|
||||||
|
}
|
||||||
|
|
||||||
private val activeRequests = mutableMapOf<Int, ActiveRequest>()
|
private val activeRequests = mutableMapOf<Int, ActiveRequest>()
|
||||||
|
|
||||||
fun attach(callback: Callback, request: Request) {
|
fun attach(callback: Callback, request: Request) {
|
||||||
@@ -90,7 +93,6 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
|
||||||
val request = activeRequests[id]!!.request
|
val request = activeRequests[id]!!.request
|
||||||
return QueryLoader(requireContext()) {
|
return QueryLoader(requireContext()) {
|
||||||
val settings = runBlocking { settingsRepository.getInitial() }
|
|
||||||
when (request) {
|
when (request) {
|
||||||
is Request.Available ->
|
is Request.Available ->
|
||||||
Database.ProductAdapter
|
Database.ProductAdapter
|
||||||
@@ -101,7 +103,6 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
section = request.section,
|
section = request.section,
|
||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it,
|
signal = it,
|
||||||
skipSignatureCheck = settings.ignoreSignature,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
is Request.Installed ->
|
is Request.Installed ->
|
||||||
@@ -113,7 +114,6 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
section = request.section,
|
section = request.section,
|
||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it,
|
signal = it,
|
||||||
skipSignatureCheck = settings.ignoreSignature,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
is Request.Updates ->
|
is Request.Updates ->
|
||||||
@@ -125,7 +125,7 @@ class CursorOwner : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|||||||
section = request.section,
|
section = request.section,
|
||||||
order = request.order,
|
order = request.order,
|
||||||
signal = it,
|
signal = it,
|
||||||
skipSignatureCheck = settings.ignoreSignature,
|
skipSignatureCheck = request.skipSignatureCheck,
|
||||||
)
|
)
|
||||||
|
|
||||||
is Request.Repositories -> Database.RepositoryAdapter.query(it)
|
is Request.Repositories -> Database.RepositoryAdapter.query(it)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.database
|
package de.felitendo.felostore.database
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -9,22 +9,22 @@ import android.os.CancellationSignal
|
|||||||
import androidx.core.database.sqlite.transaction
|
import androidx.core.database.sqlite.transaction
|
||||||
import com.fasterxml.jackson.core.JsonGenerator
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
import com.looker.droidify.database.table.DatabaseHelper
|
import de.felitendo.felostore.BuildConfig
|
||||||
import com.looker.droidify.database.table.Table
|
import de.felitendo.felostore.datastore.model.SortOrder
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
import de.felitendo.felostore.model.InstalledItem
|
||||||
import com.looker.droidify.model.InstalledItem
|
import de.felitendo.felostore.model.Product
|
||||||
import com.looker.droidify.model.Product
|
import de.felitendo.felostore.model.ProductItem
|
||||||
import com.looker.droidify.model.ProductItem
|
import de.felitendo.felostore.model.Repository
|
||||||
import com.looker.droidify.model.Repository
|
import de.felitendo.felostore.utility.common.extension.Json
|
||||||
import com.looker.droidify.utility.common.extension.Json
|
import de.felitendo.felostore.utility.common.extension.asSequence
|
||||||
import com.looker.droidify.utility.common.extension.asSequence
|
import de.felitendo.felostore.utility.common.extension.firstOrNull
|
||||||
import com.looker.droidify.utility.common.extension.firstOrNull
|
import de.felitendo.felostore.utility.common.extension.parseDictionary
|
||||||
import com.looker.droidify.utility.common.extension.parseDictionary
|
import de.felitendo.felostore.utility.common.extension.writeDictionary
|
||||||
import com.looker.droidify.utility.common.extension.writeDictionary
|
import de.felitendo.felostore.utility.common.log
|
||||||
import com.looker.droidify.utility.serialization.product
|
import de.felitendo.felostore.utility.serialization.product
|
||||||
import com.looker.droidify.utility.serialization.productItem
|
import de.felitendo.felostore.utility.serialization.productItem
|
||||||
import com.looker.droidify.utility.serialization.repository
|
import de.felitendo.felostore.utility.serialization.repository
|
||||||
import com.looker.droidify.utility.serialization.serialize
|
import de.felitendo.felostore.utility.serialization.serialize
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -44,15 +44,52 @@ import kotlin.collections.set
|
|||||||
|
|
||||||
object Database {
|
object Database {
|
||||||
fun init(context: Context): Boolean {
|
fun init(context: Context): Boolean {
|
||||||
val helper = DatabaseHelper(context)
|
val helper = Helper(context)
|
||||||
db = helper.writableDatabase
|
db = helper.writableDatabase
|
||||||
|
if (helper.created) {
|
||||||
|
for (repository in Repository.defaultRepositories.sortedBy { it.name }) {
|
||||||
|
RepositoryAdapter.put(repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
RepositoryAdapter.removeDuplicates()
|
RepositoryAdapter.removeDuplicates()
|
||||||
return helper.created || helper.updated
|
return helper.created || helper.updated
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var db: SQLiteDatabase
|
private lateinit var db: SQLiteDatabase
|
||||||
|
|
||||||
object Schema {
|
private interface Table {
|
||||||
|
val memory: Boolean
|
||||||
|
val innerName: String
|
||||||
|
val createTable: String
|
||||||
|
val createIndex: String?
|
||||||
|
get() = null
|
||||||
|
|
||||||
|
val databasePrefix: String
|
||||||
|
get() = if (memory) "memory." else ""
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
get() = "$databasePrefix$innerName"
|
||||||
|
|
||||||
|
fun formatCreateTable(name: String): String {
|
||||||
|
return buildString(128) {
|
||||||
|
append("CREATE TABLE ")
|
||||||
|
append(name)
|
||||||
|
append(" (")
|
||||||
|
trimAndJoin(createTable)
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val createIndexPairFormatted: Pair<String, String>?
|
||||||
|
get() = createIndex?.let {
|
||||||
|
Pair(
|
||||||
|
"CREATE INDEX ${innerName}_index ON $innerName ($it)",
|
||||||
|
"CREATE INDEX ${name}_index ON $innerName ($it)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Schema {
|
||||||
object Repository : Table {
|
object Repository : Table {
|
||||||
const val ROW_ID = "_id"
|
const val ROW_ID = "_id"
|
||||||
const val ROW_ENABLED = "enabled"
|
const val ROW_ENABLED = "enabled"
|
||||||
@@ -153,6 +190,126 @@ object Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Helper(context: Context) : SQLiteOpenHelper(context, "felostore", null, 5) {
|
||||||
|
var created = false
|
||||||
|
private set
|
||||||
|
var updated = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onCreate(db: SQLiteDatabase) = Unit
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) =
|
||||||
|
onVersionChange(db)
|
||||||
|
|
||||||
|
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) =
|
||||||
|
onVersionChange(db)
|
||||||
|
|
||||||
|
private fun onVersionChange(db: SQLiteDatabase) {
|
||||||
|
handleTables(db, true, Schema.Product, Schema.Category)
|
||||||
|
addRepos(db, Repository.newlyAdded)
|
||||||
|
this.updated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpen(db: SQLiteDatabase) {
|
||||||
|
val create = handleTables(db, false, Schema.Repository)
|
||||||
|
val updated = handleTables(db, create, Schema.Product, Schema.Category)
|
||||||
|
db.execSQL("ATTACH DATABASE ':memory:' AS memory")
|
||||||
|
handleTables(db, false, Schema.Installed, Schema.Lock)
|
||||||
|
handleIndexes(
|
||||||
|
db,
|
||||||
|
Schema.Repository,
|
||||||
|
Schema.Product,
|
||||||
|
Schema.Category,
|
||||||
|
Schema.Installed,
|
||||||
|
Schema.Lock,
|
||||||
|
)
|
||||||
|
dropOldTables(db, Schema.Repository, Schema.Product, Schema.Category)
|
||||||
|
this.created = this.created || create
|
||||||
|
this.updated = this.updated || create || updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleTables(db: SQLiteDatabase, recreate: Boolean, vararg tables: Table): Boolean {
|
||||||
|
val shouldRecreate = recreate || tables.any { table ->
|
||||||
|
val sql = db.query(
|
||||||
|
"${table.databasePrefix}sqlite_master",
|
||||||
|
columns = arrayOf("sql"),
|
||||||
|
selection = Pair("type = ? AND name = ?", arrayOf("table", table.innerName)),
|
||||||
|
).use { it.firstOrNull()?.getString(0) }.orEmpty()
|
||||||
|
table.formatCreateTable(table.innerName) != sql
|
||||||
|
}
|
||||||
|
return shouldRecreate && run {
|
||||||
|
val shouldVacuum = tables.map {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS ${it.name}")
|
||||||
|
db.execSQL(it.formatCreateTable(it.name))
|
||||||
|
!it.memory
|
||||||
|
}
|
||||||
|
if (shouldVacuum.any { it } && !db.inTransaction()) {
|
||||||
|
db.execSQL("VACUUM")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addRepos(db: SQLiteDatabase, repos: List<Repository>) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
log("Add Repos: $repos", "RepositoryAdapter")
|
||||||
|
}
|
||||||
|
if (repos.isEmpty()) return
|
||||||
|
db.transaction {
|
||||||
|
repos.forEach {
|
||||||
|
RepositoryAdapter.put(it, database = this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleIndexes(db: SQLiteDatabase, vararg tables: Table) {
|
||||||
|
val shouldVacuum = tables.map { table ->
|
||||||
|
val sqls = db.query(
|
||||||
|
"${table.databasePrefix}sqlite_master",
|
||||||
|
columns = arrayOf("name", "sql"),
|
||||||
|
selection = Pair("type = ? AND tbl_name = ?", arrayOf("index", table.innerName)),
|
||||||
|
)
|
||||||
|
.use { cursor ->
|
||||||
|
cursor.asSequence()
|
||||||
|
.mapNotNull { it.getString(1)?.let { sql -> Pair(it.getString(0), sql) } }
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
.filter { !it.first.startsWith("sqlite_") }
|
||||||
|
val createIndexes = table.createIndexPairFormatted?.let { listOf(it) }.orEmpty()
|
||||||
|
createIndexes.map { it.first } != sqls.map { it.second } && run {
|
||||||
|
for (name in sqls.map { it.first }) {
|
||||||
|
db.execSQL("DROP INDEX IF EXISTS $name")
|
||||||
|
}
|
||||||
|
for (createIndexPair in createIndexes) {
|
||||||
|
db.execSQL(createIndexPair.second)
|
||||||
|
}
|
||||||
|
!table.memory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldVacuum.any { it } && !db.inTransaction()) {
|
||||||
|
db.execSQL("VACUUM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dropOldTables(db: SQLiteDatabase, vararg neededTables: Table) {
|
||||||
|
val tables = db.query(
|
||||||
|
"sqlite_master",
|
||||||
|
columns = arrayOf("name"),
|
||||||
|
selection = Pair("type = ?", arrayOf("table")),
|
||||||
|
)
|
||||||
|
.use { cursor -> cursor.asSequence().mapNotNull { it.getString(0) }.toList() }
|
||||||
|
.filter { !it.startsWith("sqlite_") && !it.startsWith("android_") }
|
||||||
|
.toSet() - neededTables.mapNotNull { if (it.memory) null else it.name }.toSet()
|
||||||
|
if (tables.isNotEmpty()) {
|
||||||
|
for (table in tables) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS $table")
|
||||||
|
}
|
||||||
|
if (!db.inTransaction()) {
|
||||||
|
db.execSQL("VACUUM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class Subject {
|
sealed class Subject {
|
||||||
data object Repositories : Subject()
|
data object Repositories : Subject()
|
||||||
data class Repository(val id: Long) : Subject()
|
data class Repository(val id: Long) : Subject()
|
||||||
@@ -207,7 +364,7 @@ object Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SQLiteDatabase.query(
|
private fun SQLiteDatabase.query(
|
||||||
table: String,
|
table: String,
|
||||||
columns: Array<String>? = null,
|
columns: Array<String>? = null,
|
||||||
selection: Pair<String, Array<String>>? = null,
|
selection: Pair<String, Array<String>>? = null,
|
||||||
@@ -450,19 +607,6 @@ object Database {
|
|||||||
.map { getUpdates(skipSignatureCheck) }
|
.map { getUpdates(skipSignatureCheck) }
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
fun getAll(): List<Product> {
|
|
||||||
return db.query(
|
|
||||||
Schema.Product.name,
|
|
||||||
columns = arrayOf(
|
|
||||||
Schema.Product.ROW_REPOSITORY_ID,
|
|
||||||
Schema.Product.ROW_DESCRIPTION,
|
|
||||||
Schema.Product.ROW_DATA,
|
|
||||||
),
|
|
||||||
selection = null,
|
|
||||||
signal = null,
|
|
||||||
).use { it.asSequence().map(::transform).toList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(packageName: String, signal: CancellationSignal?): List<Product> {
|
fun get(packageName: String, signal: CancellationSignal?): List<Product> {
|
||||||
return db.query(
|
return db.query(
|
||||||
Schema.Product.name,
|
Schema.Product.name,
|
||||||
@@ -575,7 +719,7 @@ object Database {
|
|||||||
when (order) {
|
when (order) {
|
||||||
SortOrder.UPDATED -> builder += "product.${Schema.Product.ROW_UPDATED} DESC,"
|
SortOrder.UPDATED -> builder += "product.${Schema.Product.ROW_UPDATED} DESC,"
|
||||||
SortOrder.ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC,"
|
SortOrder.ADDED -> builder += "product.${Schema.Product.ROW_ADDED} DESC,"
|
||||||
else -> Unit
|
SortOrder.NAME -> Unit
|
||||||
}::class
|
}::class
|
||||||
builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC"
|
builder += "product.${Schema.Product.ROW_NAME} COLLATE LOCALIZED ASC"
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.database
|
package de.felitendo.felostore.database
|
||||||
|
|
||||||
import android.database.ContentObservable
|
import android.database.ContentObservable
|
||||||
import android.database.ContentObserver
|
import android.database.ContentObserver
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.looker.droidify.database
|
package de.felitendo.felostore.database
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import com.looker.droidify.BuildConfig
|
import de.felitendo.felostore.BuildConfig
|
||||||
import com.looker.droidify.utility.common.extension.asSequence
|
import de.felitendo.felostore.utility.common.extension.asSequence
|
||||||
import com.looker.droidify.utility.common.log
|
import de.felitendo.felostore.utility.common.log
|
||||||
|
|
||||||
class QueryBuilder {
|
class QueryBuilder {
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.database
|
package de.felitendo.felostore.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package com.looker.droidify.database
|
package de.felitendo.felostore.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.fasterxml.jackson.core.JsonToken
|
import com.fasterxml.jackson.core.JsonToken
|
||||||
import com.looker.droidify.utility.common.Exporter
|
import de.felitendo.felostore.utility.common.Exporter
|
||||||
import com.looker.droidify.utility.common.extension.Json
|
import de.felitendo.felostore.utility.common.extension.Json
|
||||||
import com.looker.droidify.utility.common.extension.forEach
|
import de.felitendo.felostore.utility.common.extension.forEach
|
||||||
import com.looker.droidify.utility.common.extension.forEachKey
|
import de.felitendo.felostore.utility.common.extension.forEachKey
|
||||||
import com.looker.droidify.utility.common.extension.parseDictionary
|
import de.felitendo.felostore.utility.common.extension.parseDictionary
|
||||||
import com.looker.droidify.utility.common.extension.writeArray
|
import de.felitendo.felostore.utility.common.extension.writeArray
|
||||||
import com.looker.droidify.utility.common.extension.writeDictionary
|
import de.felitendo.felostore.utility.common.extension.writeDictionary
|
||||||
import com.looker.droidify.di.ApplicationScope
|
import de.felitendo.felostore.di.ApplicationScope
|
||||||
import com.looker.droidify.di.IoDispatcher
|
import de.felitendo.felostore.di.IoDispatcher
|
||||||
import com.looker.droidify.model.Repository
|
import de.felitendo.felostore.model.Repository
|
||||||
import com.looker.droidify.utility.serialization.repository
|
import de.felitendo.felostore.utility.serialization.repository
|
||||||
import com.looker.droidify.utility.serialization.serialize
|
import de.felitendo.felostore.utility.serialization.serialize
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.datastore
|
package de.felitendo.felostore.datastore
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -12,26 +12,23 @@ import androidx.datastore.preferences.core.intPreferencesKey
|
|||||||
import androidx.datastore.preferences.core.longPreferencesKey
|
import androidx.datastore.preferences.core.longPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
import com.looker.droidify.datastore.model.AutoSync
|
import de.felitendo.felostore.datastore.model.AutoSync
|
||||||
import com.looker.droidify.datastore.model.InstallerType
|
import de.felitendo.felostore.datastore.model.InstallerType
|
||||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
import de.felitendo.felostore.datastore.model.ProxyPreference
|
||||||
import com.looker.droidify.datastore.model.ProxyPreference
|
import de.felitendo.felostore.datastore.model.ProxyType
|
||||||
import com.looker.droidify.datastore.model.ProxyType
|
import de.felitendo.felostore.datastore.model.SortOrder
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
import de.felitendo.felostore.datastore.model.Theme
|
||||||
import com.looker.droidify.datastore.model.Theme
|
import de.felitendo.felostore.utility.common.Exporter
|
||||||
import com.looker.droidify.utility.common.Exporter
|
import de.felitendo.felostore.utility.common.extension.updateAsMutable
|
||||||
import com.looker.droidify.utility.common.extension.updateAsMutable
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlin.time.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.hours
|
import kotlin.time.Duration.Companion.hours
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
import kotlin.time.Instant
|
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
|
||||||
class PreferenceSettingsRepository(
|
class PreferenceSettingsRepository(
|
||||||
private val dataStore: DataStore<Preferences>,
|
private val dataStore: DataStore<Preferences>,
|
||||||
private val exporter: Exporter<Settings>,
|
private val exporter: Exporter<Settings>,
|
||||||
@@ -39,7 +36,7 @@ class PreferenceSettingsRepository(
|
|||||||
override val data: Flow<Settings> = dataStore.data
|
override val data: Flow<Settings> = dataStore.data
|
||||||
.catch { exception ->
|
.catch { exception ->
|
||||||
if (exception is IOException) {
|
if (exception is IOException) {
|
||||||
Log.e("PreferencesSettingsRepository", "Error reading preferences.", exception)
|
Log.e("TAG", "Error reading preferences.", exception)
|
||||||
} else {
|
} else {
|
||||||
throw exception
|
throw exception
|
||||||
}
|
}
|
||||||
@@ -88,31 +85,6 @@ class PreferenceSettingsRepository(
|
|||||||
override suspend fun setInstallerType(installerType: InstallerType) =
|
override suspend fun setInstallerType(installerType: InstallerType) =
|
||||||
INSTALLER_TYPE.update(installerType.name)
|
INSTALLER_TYPE.update(installerType.name)
|
||||||
|
|
||||||
override suspend fun setLegacyInstallerComponent(component: LegacyInstallerComponent?) {
|
|
||||||
when (component) {
|
|
||||||
null -> {
|
|
||||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
|
|
||||||
}
|
|
||||||
is LegacyInstallerComponent.Component -> {
|
|
||||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("component")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_CLASS.update(component.clazz)
|
|
||||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update(component.activity)
|
|
||||||
}
|
|
||||||
LegacyInstallerComponent.Unspecified -> {
|
|
||||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("unspecified")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
|
|
||||||
}
|
|
||||||
LegacyInstallerComponent.AlwaysChoose -> {
|
|
||||||
LEGACY_INSTALLER_COMPONENT_TYPE.update("always_choose")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_CLASS.update("")
|
|
||||||
LEGACY_INSTALLER_COMPONENT_ACTIVITY.update("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setAutoUpdate(allow: Boolean) =
|
override suspend fun setAutoUpdate(allow: Boolean) =
|
||||||
AUTO_UPDATE.update(allow)
|
AUTO_UPDATE.update(allow)
|
||||||
|
|
||||||
@@ -153,18 +125,6 @@ class PreferenceSettingsRepository(
|
|||||||
private fun mapSettings(preferences: Preferences): Settings {
|
private fun mapSettings(preferences: Preferences): Settings {
|
||||||
val installerType =
|
val installerType =
|
||||||
InstallerType.valueOf(preferences[INSTALLER_TYPE] ?: InstallerType.Default.name)
|
InstallerType.valueOf(preferences[INSTALLER_TYPE] ?: InstallerType.Default.name)
|
||||||
val legacyInstallerComponent = when (preferences[LEGACY_INSTALLER_COMPONENT_TYPE]) {
|
|
||||||
"component" -> {
|
|
||||||
preferences[LEGACY_INSTALLER_COMPONENT_CLASS]?.takeIf { it.isNotBlank() }?.let { cls ->
|
|
||||||
preferences[LEGACY_INSTALLER_COMPONENT_ACTIVITY]?.takeIf { it.isNotBlank() }?.let { act ->
|
|
||||||
LegacyInstallerComponent.Component(cls, act)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"unspecified" -> LegacyInstallerComponent.Unspecified
|
|
||||||
"always_choose" -> LegacyInstallerComponent.AlwaysChoose
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
val language = preferences[LANGUAGE] ?: "system"
|
val language = preferences[LANGUAGE] ?: "system"
|
||||||
val incompatibleVersions = preferences[INCOMPATIBLE_VERSIONS] ?: false
|
val incompatibleVersions = preferences[INCOMPATIBLE_VERSIONS] ?: false
|
||||||
@@ -194,7 +154,6 @@ class PreferenceSettingsRepository(
|
|||||||
theme = theme,
|
theme = theme,
|
||||||
dynamicTheme = dynamicTheme,
|
dynamicTheme = dynamicTheme,
|
||||||
installerType = installerType,
|
installerType = installerType,
|
||||||
legacyInstallerComponent = legacyInstallerComponent,
|
|
||||||
autoUpdate = autoUpdate,
|
autoUpdate = autoUpdate,
|
||||||
autoSync = autoSync,
|
autoSync = autoSync,
|
||||||
sortOrder = sortOrder,
|
sortOrder = sortOrder,
|
||||||
@@ -226,9 +185,6 @@ class PreferenceSettingsRepository(
|
|||||||
val LAST_CLEAN_UP = longPreferencesKey("key_last_clean_up_time")
|
val LAST_CLEAN_UP = longPreferencesKey("key_last_clean_up_time")
|
||||||
val FAVOURITE_APPS = stringSetPreferencesKey("key_favourite_apps")
|
val FAVOURITE_APPS = stringSetPreferencesKey("key_favourite_apps")
|
||||||
val HOME_SCREEN_SWIPING = booleanPreferencesKey("key_home_swiping")
|
val HOME_SCREEN_SWIPING = booleanPreferencesKey("key_home_swiping")
|
||||||
val LEGACY_INSTALLER_COMPONENT_CLASS = stringPreferencesKey("key_legacy_installer_component_class")
|
|
||||||
val LEGACY_INSTALLER_COMPONENT_ACTIVITY = stringPreferencesKey("key_legacy_installer_component_activity")
|
|
||||||
val LEGACY_INSTALLER_COMPONENT_TYPE = stringPreferencesKey("key_legacy_installer_component_type")
|
|
||||||
|
|
||||||
// Enums
|
// Enums
|
||||||
val THEME = stringPreferencesKey("key_theme")
|
val THEME = stringPreferencesKey("key_theme")
|
||||||
@@ -244,28 +200,6 @@ class PreferenceSettingsRepository(
|
|||||||
set(UNSTABLE_UPDATES, settings.unstableUpdate)
|
set(UNSTABLE_UPDATES, settings.unstableUpdate)
|
||||||
set(THEME, settings.theme.name)
|
set(THEME, settings.theme.name)
|
||||||
set(DYNAMIC_THEME, settings.dynamicTheme)
|
set(DYNAMIC_THEME, settings.dynamicTheme)
|
||||||
when (settings.legacyInstallerComponent) {
|
|
||||||
is LegacyInstallerComponent.Component -> {
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "component")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, settings.legacyInstallerComponent.clazz)
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, settings.legacyInstallerComponent.activity)
|
|
||||||
}
|
|
||||||
LegacyInstallerComponent.Unspecified -> {
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "unspecified")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
|
|
||||||
}
|
|
||||||
LegacyInstallerComponent.AlwaysChoose -> {
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "always_choose")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
|
|
||||||
}
|
|
||||||
null -> {
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_TYPE, "")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_CLASS, "")
|
|
||||||
set(LEGACY_INSTALLER_COMPONENT_ACTIVITY, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set(INSTALLER_TYPE, settings.installerType.name)
|
set(INSTALLER_TYPE, settings.installerType.name)
|
||||||
set(AUTO_UPDATE, settings.autoUpdate)
|
set(AUTO_UPDATE, settings.autoUpdate)
|
||||||
set(AUTO_SYNC, settings.autoSync.name)
|
set(AUTO_SYNC, settings.autoSync.name)
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
package com.looker.droidify.datastore
|
package de.felitendo.felostore.datastore
|
||||||
|
|
||||||
import androidx.datastore.core.Serializer
|
import androidx.datastore.core.Serializer
|
||||||
import com.looker.droidify.datastore.model.AutoSync
|
import de.felitendo.felostore.datastore.model.AutoSync
|
||||||
import com.looker.droidify.datastore.model.InstallerType
|
import de.felitendo.felostore.datastore.model.InstallerType
|
||||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
import de.felitendo.felostore.datastore.model.ProxyPreference
|
||||||
import com.looker.droidify.datastore.model.ProxyPreference
|
import de.felitendo.felostore.datastore.model.SortOrder
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
import de.felitendo.felostore.datastore.model.Theme
|
||||||
import com.looker.droidify.datastore.model.Theme
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import kotlinx.serialization.json.encodeToStream
|
import kotlinx.serialization.json.encodeToStream
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.hours
|
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
import kotlin.time.Instant
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@OptIn(ExperimentalTime::class)
|
|
||||||
data class Settings(
|
data class Settings(
|
||||||
val language: String = "system",
|
val language: String = "system",
|
||||||
val incompatibleVersions: Boolean = false,
|
val incompatibleVersions: Boolean = false,
|
||||||
@@ -30,10 +27,9 @@ data class Settings(
|
|||||||
val unstableUpdate: Boolean = false,
|
val unstableUpdate: Boolean = false,
|
||||||
val ignoreSignature: Boolean = false,
|
val ignoreSignature: Boolean = false,
|
||||||
val theme: Theme = Theme.SYSTEM,
|
val theme: Theme = Theme.SYSTEM,
|
||||||
val dynamicTheme: Boolean = false,
|
val dynamicTheme: Boolean = true,
|
||||||
val installerType: InstallerType = InstallerType.Default,
|
val installerType: InstallerType = InstallerType.Default,
|
||||||
val legacyInstallerComponent: LegacyInstallerComponent? = null,
|
val autoUpdate: Boolean = true,
|
||||||
val autoUpdate: Boolean = false,
|
|
||||||
val autoSync: AutoSync = AutoSync.WIFI_ONLY,
|
val autoSync: AutoSync = AutoSync.WIFI_ONLY,
|
||||||
val sortOrder: SortOrder = SortOrder.UPDATED,
|
val sortOrder: SortOrder = SortOrder.UPDATED,
|
||||||
val proxy: ProxyPreference = ProxyPreference(),
|
val proxy: ProxyPreference = ProxyPreference(),
|
||||||
@@ -48,7 +44,6 @@ object SettingsSerializer : Serializer<Settings> {
|
|||||||
|
|
||||||
private val json = Json { encodeDefaults = true }
|
private val json = Json { encodeDefaults = true }
|
||||||
|
|
||||||
@OptIn(ExperimentalTime::class)
|
|
||||||
override val defaultValue: Settings = Settings()
|
override val defaultValue: Settings = Settings()
|
||||||
|
|
||||||
override suspend fun readFrom(input: InputStream): Settings {
|
override suspend fun readFrom(input: InputStream): Settings {
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
package com.looker.droidify.datastore
|
package de.felitendo.felostore.datastore
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.looker.droidify.datastore.model.AutoSync
|
import de.felitendo.felostore.datastore.model.AutoSync
|
||||||
import com.looker.droidify.datastore.model.InstallerType
|
import de.felitendo.felostore.datastore.model.InstallerType
|
||||||
import com.looker.droidify.datastore.model.LegacyInstallerComponent
|
import de.felitendo.felostore.datastore.model.ProxyType
|
||||||
import com.looker.droidify.datastore.model.ProxyType
|
import de.felitendo.felostore.datastore.model.SortOrder
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
import de.felitendo.felostore.datastore.model.Theme
|
||||||
import com.looker.droidify.datastore.model.Theme
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -38,8 +37,6 @@ interface SettingsRepository {
|
|||||||
|
|
||||||
suspend fun setInstallerType(installerType: InstallerType)
|
suspend fun setInstallerType(installerType: InstallerType)
|
||||||
|
|
||||||
suspend fun setLegacyInstallerComponent(component: LegacyInstallerComponent?)
|
|
||||||
|
|
||||||
suspend fun setAutoUpdate(allow: Boolean)
|
suspend fun setAutoUpdate(allow: Boolean)
|
||||||
|
|
||||||
suspend fun setAutoSync(autoSync: AutoSync)
|
suspend fun setAutoSync(autoSync: AutoSync)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.looker.droidify.datastore.exporter
|
package de.felitendo.felostore.datastore.exporter
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.looker.droidify.utility.common.Exporter
|
import de.felitendo.felostore.utility.common.Exporter
|
||||||
import com.looker.droidify.datastore.Settings
|
import de.felitendo.felostore.datastore.Settings
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package com.looker.droidify.datastore.extension
|
package de.felitendo.felostore.datastore.extension
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import com.looker.droidify.R
|
import de.felitendo.felostore.R
|
||||||
import com.looker.droidify.R.string as stringRes
|
import de.felitendo.felostore.R.string as stringRes
|
||||||
import com.looker.droidify.R.style as styleRes
|
import de.felitendo.felostore.R.style as styleRes
|
||||||
import com.looker.droidify.utility.common.SdkCheck
|
import de.felitendo.felostore.utility.common.SdkCheck
|
||||||
import com.looker.droidify.datastore.model.AutoSync
|
import de.felitendo.felostore.datastore.model.AutoSync
|
||||||
import com.looker.droidify.datastore.model.InstallerType
|
import de.felitendo.felostore.datastore.model.InstallerType
|
||||||
import com.looker.droidify.datastore.model.ProxyType
|
import de.felitendo.felostore.datastore.model.ProxyType
|
||||||
import com.looker.droidify.datastore.model.SortOrder
|
import de.felitendo.felostore.datastore.model.SortOrder
|
||||||
import com.looker.droidify.datastore.model.Theme
|
import de.felitendo.felostore.datastore.model.Theme
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
fun Configuration.getThemeRes(theme: Theme, dynamicTheme: Boolean) = when (theme) {
|
fun Configuration.getThemeRes(theme: Theme, dynamicTheme: Boolean) = when (theme) {
|
||||||
@@ -92,7 +92,7 @@ fun Context?.sortOrderName(sortOrder: SortOrder) = this?.let {
|
|||||||
SortOrder.UPDATED -> getString(stringRes.recently_updated)
|
SortOrder.UPDATED -> getString(stringRes.recently_updated)
|
||||||
SortOrder.ADDED -> getString(stringRes.whats_new)
|
SortOrder.ADDED -> getString(stringRes.whats_new)
|
||||||
SortOrder.NAME -> getString(stringRes.name)
|
SortOrder.NAME -> getString(stringRes.name)
|
||||||
SortOrder.SIZE -> getString(stringRes.size)
|
// SortOrder.SIZE -> getString(stringRes.size)
|
||||||
}
|
}
|
||||||
} ?: ""
|
} ?: ""
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.looker.droidify.datastore.migration
|
package de.felitendo.felostore.datastore.migration
|
||||||
|
|
||||||
import com.looker.droidify.datastore.PreferenceSettingsRepository.PreferencesKeys.setting
|
import de.felitendo.felostore.datastore.PreferenceSettingsRepository.PreferencesKeys.setting
|
||||||
import com.looker.droidify.datastore.Settings
|
import de.felitendo.felostore.datastore.Settings
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
class ProtoToPreferenceMigration(
|
class ProtoToPreferenceMigration(
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.datastore.model
|
package de.felitendo.felostore.datastore.model
|
||||||
|
|
||||||
enum class AutoSync {
|
enum class AutoSync {
|
||||||
ALWAYS,
|
ALWAYS,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.looker.droidify.datastore.model
|
package de.felitendo.felostore.datastore.model
|
||||||
|
|
||||||
import com.looker.droidify.utility.common.device.Miui
|
import de.felitendo.felostore.utility.common.device.Miui
|
||||||
|
|
||||||
enum class InstallerType {
|
enum class InstallerType {
|
||||||
LEGACY,
|
LEGACY,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.datastore.model
|
package de.felitendo.felostore.datastore.model
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.datastore.model
|
package de.felitendo.felostore.datastore.model
|
||||||
|
|
||||||
enum class ProxyType {
|
enum class ProxyType {
|
||||||
DIRECT,
|
DIRECT,
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package de.felitendo.felostore.datastore.model
|
||||||
|
|
||||||
|
// todo: Add Support for sorting by size
|
||||||
|
enum class SortOrder {
|
||||||
|
UPDATED,
|
||||||
|
ADDED,
|
||||||
|
NAME
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.datastore.model
|
package de.felitendo.felostore.datastore.model
|
||||||
|
|
||||||
enum class Theme {
|
enum class Theme {
|
||||||
SYSTEM,
|
SYSTEM,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.di
|
package de.felitendo.felostore.di
|
||||||
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.di
|
package de.felitendo.felostore.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
@@ -7,13 +7,13 @@ import androidx.datastore.dataStoreFile
|
|||||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||||
import com.looker.droidify.utility.common.Exporter
|
import de.felitendo.felostore.utility.common.Exporter
|
||||||
import com.looker.droidify.datastore.PreferenceSettingsRepository
|
import de.felitendo.felostore.datastore.PreferenceSettingsRepository
|
||||||
import com.looker.droidify.datastore.Settings
|
import de.felitendo.felostore.datastore.Settings
|
||||||
import com.looker.droidify.datastore.SettingsRepository
|
import de.felitendo.felostore.datastore.SettingsRepository
|
||||||
import com.looker.droidify.datastore.SettingsSerializer
|
import de.felitendo.felostore.datastore.SettingsSerializer
|
||||||
import com.looker.droidify.datastore.exporter.SettingsExporter
|
import de.felitendo.felostore.datastore.exporter.SettingsExporter
|
||||||
import com.looker.droidify.datastore.migration.ProtoToPreferenceMigration
|
import de.felitendo.felostore.datastore.migration.ProtoToPreferenceMigration
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.looker.droidify.di
|
package de.felitendo.felostore.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.looker.droidify.datastore.SettingsRepository
|
import de.felitendo.felostore.datastore.SettingsRepository
|
||||||
import com.looker.droidify.installer.InstallManager
|
import de.felitendo.felostore.installer.InstallManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.looker.droidify.di
|
package de.felitendo.felostore.di
|
||||||
|
|
||||||
import com.looker.droidify.network.Downloader
|
import de.felitendo.felostore.network.Downloader
|
||||||
import com.looker.droidify.network.KtorDownloader
|
import de.felitendo.felostore.network.KtorDownloader
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.looker.droidify.domain
|
package de.felitendo.felostore.domain
|
||||||
|
|
||||||
import com.looker.droidify.domain.model.App
|
import de.felitendo.felostore.domain.model.App
|
||||||
import com.looker.droidify.domain.model.AppMinimal
|
import de.felitendo.felostore.domain.model.AppMinimal
|
||||||
import com.looker.droidify.domain.model.Author
|
import de.felitendo.felostore.domain.model.Author
|
||||||
import com.looker.droidify.domain.model.Package
|
import de.felitendo.felostore.domain.model.Package
|
||||||
import com.looker.droidify.domain.model.PackageName
|
import de.felitendo.felostore.domain.model.PackageName
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AppRepository {
|
interface AppRepository {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.looker.droidify.domain
|
package de.felitendo.felostore.domain
|
||||||
|
|
||||||
import com.looker.droidify.domain.model.Repo
|
import de.felitendo.felostore.domain.model.Repo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface RepoRepository {
|
interface RepoRepository {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.domain.model
|
package de.felitendo.felostore.domain.model
|
||||||
|
|
||||||
data class App(
|
data class App(
|
||||||
val repoId: Long,
|
val repoId: Long,
|
||||||
@@ -6,7 +6,7 @@ data class App(
|
|||||||
val categories: List<String>,
|
val categories: List<String>,
|
||||||
val links: Links,
|
val links: Links,
|
||||||
val metadata: Metadata,
|
val metadata: Metadata,
|
||||||
val author: Author?,
|
val author: Author,
|
||||||
val screenshots: Screenshots,
|
val screenshots: Screenshots,
|
||||||
val graphics: Graphics,
|
val graphics: Graphics,
|
||||||
val donation: Donation,
|
val donation: Donation,
|
||||||
@@ -15,35 +15,34 @@ data class App(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class Author(
|
data class Author(
|
||||||
val id: Int,
|
val id: Long,
|
||||||
val name: String?,
|
val name: String,
|
||||||
val email: String?,
|
val email: String,
|
||||||
val phone: String?,
|
val web: String
|
||||||
val web: String?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Donation(
|
data class Donation(
|
||||||
val regularUrl: List<String>? = null,
|
val regularUrl: String? = null,
|
||||||
val bitcoinAddress: String? = null,
|
val bitcoinAddress: String? = null,
|
||||||
val flattrId: String? = null,
|
val flattrId: String? = null,
|
||||||
val litecoinAddress: String? = null,
|
val liteCoinAddress: String? = null,
|
||||||
val openCollectiveId: String? = null,
|
val openCollectiveId: String? = null,
|
||||||
val liberapayId: String? = null,
|
val librePayId: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Graphics(
|
data class Graphics(
|
||||||
val featureGraphic: String? = null,
|
val featureGraphic: String = "",
|
||||||
val promoGraphic: String? = null,
|
val promoGraphic: String = "",
|
||||||
val tvBanner: String? = null,
|
val tvBanner: String = "",
|
||||||
val video: String? = null,
|
val video: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Links(
|
data class Links(
|
||||||
val changelog: String? = null,
|
val changelog: String = "",
|
||||||
val issueTracker: String? = null,
|
val issueTracker: String = "",
|
||||||
val sourceCode: String? = null,
|
val sourceCode: String = "",
|
||||||
val translation: String? = null,
|
val translation: String = "",
|
||||||
val webSite: String? = null,
|
val webSite: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.domain.model
|
package de.felitendo.felostore.domain.model
|
||||||
|
|
||||||
interface DataFile {
|
interface DataFile {
|
||||||
val name: String
|
val name: String
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.domain.model
|
package de.felitendo.felostore.domain.model
|
||||||
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
@@ -27,22 +27,6 @@ fun ByteArray.hex(): String = joinToString(separator = "") { byte ->
|
|||||||
fun Fingerprint.formattedString(): String = value.windowed(2, 2, false)
|
fun Fingerprint.formattedString(): String = value.windowed(2, 2, false)
|
||||||
.take(FINGERPRINT_LENGTH / 2).joinToString(separator = " ") { it.uppercase(Locale.US) }
|
.take(FINGERPRINT_LENGTH / 2).joinToString(separator = " ") { it.uppercase(Locale.US) }
|
||||||
|
|
||||||
fun String.fingerprint(): Fingerprint = Fingerprint(
|
|
||||||
MessageDigest.getInstance("SHA-256")
|
|
||||||
.digest(
|
|
||||||
this
|
|
||||||
.chunked(2)
|
|
||||||
.mapNotNull { byteStr ->
|
|
||||||
try {
|
|
||||||
byteStr.toInt(16).toByte()
|
|
||||||
} catch (_: NumberFormatException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toByteArray(),
|
|
||||||
).hex(),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Certificate.fingerprint(): Fingerprint {
|
fun Certificate.fingerprint(): Fingerprint {
|
||||||
val bytes = encoded
|
val bytes = encoded
|
||||||
return if (bytes.size >= 256) {
|
return if (bytes.size >= 256) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.domain.model
|
package de.felitendo.felostore.domain.model
|
||||||
|
|
||||||
data class Package(
|
data class Package(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.domain.model
|
package de.felitendo.felostore.domain.model
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class PackageName(val name: String)
|
value class PackageName(val name: String)
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
package com.looker.droidify.domain.model
|
package de.felitendo.felostore.domain.model
|
||||||
|
|
||||||
data class Repo(
|
data class Repo(
|
||||||
val id: Int,
|
val id: Long,
|
||||||
val enabled: Boolean,
|
val enabled: Boolean,
|
||||||
val address: String,
|
val address: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val fingerprint: Fingerprint?,
|
val fingerprint: Fingerprint?,
|
||||||
val authentication: Authentication?,
|
val authentication: Authentication,
|
||||||
val versionInfo: VersionInfo,
|
val versionInfo: VersionInfo,
|
||||||
val mirrors: List<String>,
|
val mirrors: List<String>,
|
||||||
|
val antiFeatures: List<AntiFeature>,
|
||||||
|
val categories: List<Category>
|
||||||
) {
|
) {
|
||||||
val shouldAuthenticate = authentication != null
|
val shouldAuthenticate =
|
||||||
|
authentication.username.isNotEmpty() && authentication.password.isNotEmpty()
|
||||||
|
|
||||||
fun update(fingerprint: Fingerprint, timestamp: Long? = null, etag: String? = null): Repo {
|
fun update(fingerprint: Fingerprint, timestamp: Long? = null, etag: String? = null): Repo {
|
||||||
return copy(
|
return copy(
|
||||||
fingerprint = fingerprint,
|
fingerprint = fingerprint,
|
||||||
versionInfo = timestamp?.let { VersionInfo(timestamp = it, etag = etag) }
|
versionInfo = timestamp?.let { VersionInfo(timestamp = it, etag = etag) } ?: versionInfo
|
||||||
?: versionInfo,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,22 +28,22 @@ data class AntiFeature(
|
|||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val icon: String = "",
|
val icon: String = "",
|
||||||
val description: String = "",
|
val description: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Category(
|
data class Category(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val icon: String = "",
|
val icon: String = "",
|
||||||
val description: String = "",
|
val description: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Authentication(
|
data class Authentication(
|
||||||
val username: String,
|
val username: String,
|
||||||
val password: String,
|
val password: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class VersionInfo(
|
data class VersionInfo(
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
val etag: String?,
|
val etag: String?
|
||||||
)
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.graphics
|
package de.felitendo.felostore.graphics
|
||||||
|
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.ColorFilter
|
import android.graphics.ColorFilter
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.looker.droidify.graphics
|
package de.felitendo.felostore.graphics
|
||||||
|
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
package com.looker.droidify.index
|
package de.felitendo.felostore.index
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import com.fasterxml.jackson.core.JsonToken
|
import com.fasterxml.jackson.core.JsonToken
|
||||||
import com.looker.droidify.utility.common.extension.Json
|
import de.felitendo.felostore.utility.common.extension.Json
|
||||||
import com.looker.droidify.utility.common.extension.asSequence
|
import de.felitendo.felostore.utility.common.extension.asSequence
|
||||||
import com.looker.droidify.utility.common.extension.collectNotNull
|
import de.felitendo.felostore.utility.common.extension.collectNotNull
|
||||||
import com.looker.droidify.utility.common.extension.writeDictionary
|
import de.felitendo.felostore.utility.common.extension.writeDictionary
|
||||||
import com.looker.droidify.model.Product
|
import de.felitendo.felostore.model.Product
|
||||||
import com.looker.droidify.model.Release
|
import de.felitendo.felostore.model.Release
|
||||||
import com.looker.droidify.utility.serialization.product
|
import de.felitendo.felostore.utility.serialization.product
|
||||||
import com.looker.droidify.utility.serialization.release
|
import de.felitendo.felostore.utility.serialization.release
|
||||||
import com.looker.droidify.utility.serialization.serialize
|
import de.felitendo.felostore.utility.serialization.serialize
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
package com.looker.droidify.index
|
package de.felitendo.felostore.index
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.core.os.ConfigurationCompat.getLocales
|
import androidx.core.os.ConfigurationCompat.getLocales
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
import com.fasterxml.jackson.core.JsonToken
|
import com.fasterxml.jackson.core.JsonToken
|
||||||
import com.looker.droidify.utility.common.extension.Json
|
import de.felitendo.felostore.utility.common.extension.Json
|
||||||
import com.looker.droidify.utility.common.extension.collectDistinctNotEmptyStrings
|
import de.felitendo.felostore.utility.common.extension.collectDistinctNotEmptyStrings
|
||||||
import com.looker.droidify.utility.common.extension.collectNotNull
|
import de.felitendo.felostore.utility.common.extension.collectNotNull
|
||||||
import com.looker.droidify.utility.common.extension.forEach
|
import de.felitendo.felostore.utility.common.extension.forEach
|
||||||
import com.looker.droidify.utility.common.extension.forEachKey
|
import de.felitendo.felostore.utility.common.extension.forEachKey
|
||||||
import com.looker.droidify.utility.common.extension.illegal
|
import de.felitendo.felostore.utility.common.extension.illegal
|
||||||
import com.looker.droidify.model.Product
|
import de.felitendo.felostore.model.Product
|
||||||
import com.looker.droidify.model.Product.Donate.Bitcoin
|
import de.felitendo.felostore.model.Product.Donate.Bitcoin
|
||||||
import com.looker.droidify.model.Product.Donate.Liberapay
|
import de.felitendo.felostore.model.Product.Donate.Liberapay
|
||||||
import com.looker.droidify.model.Product.Donate.Litecoin
|
import de.felitendo.felostore.model.Product.Donate.Litecoin
|
||||||
import com.looker.droidify.model.Product.Donate.OpenCollective
|
import de.felitendo.felostore.model.Product.Donate.OpenCollective
|
||||||
import com.looker.droidify.model.Product.Donate.Regular
|
import de.felitendo.felostore.model.Product.Donate.Regular
|
||||||
import com.looker.droidify.model.Product.Screenshot.Type.LARGE_TABLET
|
import de.felitendo.felostore.model.Product.Screenshot.Type.LARGE_TABLET
|
||||||
import com.looker.droidify.model.Product.Screenshot.Type.PHONE
|
import de.felitendo.felostore.model.Product.Screenshot.Type.PHONE
|
||||||
import com.looker.droidify.model.Product.Screenshot.Type.SMALL_TABLET
|
import de.felitendo.felostore.model.Product.Screenshot.Type.SMALL_TABLET
|
||||||
import com.looker.droidify.model.Product.Screenshot.Type.TV
|
import de.felitendo.felostore.model.Product.Screenshot.Type.TV
|
||||||
import com.looker.droidify.model.Product.Screenshot.Type.VIDEO
|
import de.felitendo.felostore.model.Product.Screenshot.Type.VIDEO
|
||||||
import com.looker.droidify.model.Product.Screenshot.Type.WEAR
|
import de.felitendo.felostore.model.Product.Screenshot.Type.WEAR
|
||||||
import com.looker.droidify.model.Release
|
import de.felitendo.felostore.model.Release
|
||||||
import com.looker.droidify.utility.common.SdkCheck
|
import de.felitendo.felostore.utility.common.SdkCheck
|
||||||
import com.looker.droidify.utility.common.nullIfEmpty
|
import de.felitendo.felostore.utility.common.nullIfEmpty
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
object IndexV1Parser {
|
object IndexV1Parser {
|
||||||
@@ -1,27 +1,24 @@
|
|||||||
package com.looker.droidify.index
|
package de.felitendo.felostore.index
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.net.toUri
|
import android.net.Uri
|
||||||
import com.looker.droidify.database.Database
|
import de.felitendo.felostore.database.Database
|
||||||
import com.looker.droidify.domain.model.fingerprint
|
import de.felitendo.felostore.domain.model.fingerprint
|
||||||
import com.looker.droidify.model.Product
|
import de.felitendo.felostore.model.Product
|
||||||
import com.looker.droidify.model.Release
|
import de.felitendo.felostore.model.Release
|
||||||
import com.looker.droidify.model.Repository
|
import de.felitendo.felostore.model.Repository
|
||||||
import com.looker.droidify.network.Downloader
|
import de.felitendo.felostore.network.Downloader
|
||||||
import com.looker.droidify.network.NetworkResponse
|
import de.felitendo.felostore.network.NetworkResponse
|
||||||
import com.looker.droidify.utility.common.SdkCheck
|
import de.felitendo.felostore.utility.common.SdkCheck
|
||||||
import com.looker.droidify.utility.common.cache.Cache
|
import de.felitendo.felostore.utility.common.cache.Cache
|
||||||
import com.looker.droidify.utility.common.extension.toFormattedString
|
import de.felitendo.felostore.utility.common.extension.toFormattedString
|
||||||
import com.looker.droidify.utility.common.result.Result
|
import de.felitendo.felostore.utility.common.result.Result
|
||||||
import com.looker.droidify.utility.extension.android.Android
|
import de.felitendo.felostore.utility.extension.android.Android
|
||||||
import com.looker.droidify.utility.getProgress
|
import de.felitendo.felostore.utility.getProgress
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.CodeSigner
|
import java.security.CodeSigner
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
@@ -36,7 +33,7 @@ object RepositoryUpdater {
|
|||||||
// TODO Add support for Index-V2 and also cleanup everything here
|
// TODO Add support for Index-V2 and also cleanup everything here
|
||||||
enum class IndexType(
|
enum class IndexType(
|
||||||
val jarName: String,
|
val jarName: String,
|
||||||
val contentName: String,
|
val contentName: String
|
||||||
) {
|
) {
|
||||||
INDEX_V1("index-v1.jar", "index-v1.json")
|
INDEX_V1("index-v1.jar", "index-v1.json")
|
||||||
}
|
}
|
||||||
@@ -54,7 +51,7 @@ object RepositoryUpdater {
|
|||||||
|
|
||||||
constructor(errorType: ErrorType, message: String, cause: Exception) : super(
|
constructor(errorType: ErrorType, message: String, cause: Exception) : super(
|
||||||
message,
|
message,
|
||||||
cause,
|
cause
|
||||||
) {
|
) {
|
||||||
this.errorType = errorType
|
this.errorType = errorType
|
||||||
}
|
}
|
||||||
@@ -92,13 +89,13 @@ object RepositoryUpdater {
|
|||||||
context: Context,
|
context: Context,
|
||||||
repository: Repository,
|
repository: Repository,
|
||||||
unstable: Boolean,
|
unstable: Boolean,
|
||||||
callback: (Stage, Long, Long?) -> Unit,
|
callback: (Stage, Long, Long?) -> Unit
|
||||||
) = update(
|
) = update(
|
||||||
context = context,
|
context = context,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
unstable = unstable,
|
unstable = unstable,
|
||||||
indexTypes = listOf(IndexType.INDEX_V1),
|
indexTypes = listOf(IndexType.INDEX_V1),
|
||||||
callback = callback,
|
callback = callback
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun update(
|
private suspend fun update(
|
||||||
@@ -106,7 +103,7 @@ object RepositoryUpdater {
|
|||||||
repository: Repository,
|
repository: Repository,
|
||||||
unstable: Boolean,
|
unstable: Boolean,
|
||||||
indexTypes: List<IndexType>,
|
indexTypes: List<IndexType>,
|
||||||
callback: (Stage, Long, Long?) -> Unit,
|
callback: (Stage, Long, Long?) -> Unit
|
||||||
): Result<Boolean> = withContext(Dispatchers.IO) {
|
): Result<Boolean> = withContext(Dispatchers.IO) {
|
||||||
val indexType = indexTypes[0]
|
val indexType = indexTypes[0]
|
||||||
when (val request = downloadIndex(context, repository, indexType, callback)) {
|
when (val request = downloadIndex(context, repository, indexType, callback)) {
|
||||||
@@ -123,14 +120,14 @@ object RepositoryUpdater {
|
|||||||
repository = repository,
|
repository = repository,
|
||||||
indexTypes = indexTypes.subList(1, indexTypes.size),
|
indexTypes = indexTypes.subList(1, indexTypes.size),
|
||||||
unstable = unstable,
|
unstable = unstable,
|
||||||
callback = callback,
|
callback = callback
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Result.Error(
|
Result.Error(
|
||||||
UpdateException(
|
UpdateException(
|
||||||
ErrorType.HTTP,
|
ErrorType.HTTP,
|
||||||
"Invalid response: HTTP ${result.statusCode}",
|
"Invalid response: HTTP ${result.statusCode}"
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +146,7 @@ object RepositoryUpdater {
|
|||||||
file = request.data.file,
|
file = request.data.file,
|
||||||
lastModified = request.data.lastModified,
|
lastModified = request.data.lastModified,
|
||||||
entityTag = request.data.entityTag,
|
entityTag = request.data.entityTag,
|
||||||
callback = callback,
|
callback = callback
|
||||||
)
|
)
|
||||||
Result.Success(isFileParsedSuccessfully)
|
Result.Success(isFileParsedSuccessfully)
|
||||||
} catch (e: UpdateException) {
|
} catch (e: UpdateException) {
|
||||||
@@ -164,20 +161,20 @@ object RepositoryUpdater {
|
|||||||
context: Context,
|
context: Context,
|
||||||
repository: Repository,
|
repository: Repository,
|
||||||
indexType: IndexType,
|
indexType: IndexType,
|
||||||
callback: (Stage, Long, Long?) -> Unit,
|
callback: (Stage, Long, Long?) -> Unit
|
||||||
): Result<IndexFile> = withContext(Dispatchers.IO) {
|
): Result<IndexFile> = withContext(Dispatchers.IO) {
|
||||||
val file = Cache.getTemporaryFile(context)
|
val file = Cache.getTemporaryFile(context)
|
||||||
val result = downloader.downloadToFile(
|
val result = downloader.downloadToFile(
|
||||||
url = repository.address.toUri().buildUpon()
|
url = Uri.parse(repository.address).buildUpon()
|
||||||
.appendPath(indexType.jarName).build().toString(),
|
.appendPath(indexType.jarName).build().toString(),
|
||||||
target = file,
|
target = file,
|
||||||
headers = {
|
headers = {
|
||||||
ifModifiedSince(repository.lastModified)
|
ifModifiedSince(repository.lastModified)
|
||||||
etag(repository.entityTag)
|
etag(repository.entityTag)
|
||||||
authentication(repository.authentication)
|
authentication(repository.authentication)
|
||||||
},
|
}
|
||||||
) { read, total ->
|
) { read, total ->
|
||||||
callback(Stage.DOWNLOAD, read.value, total?.value)
|
callback(Stage.DOWNLOAD, read.value, total.value.takeIf { it != 0L })
|
||||||
}
|
}
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
@@ -188,8 +185,8 @@ object RepositoryUpdater {
|
|||||||
lastModified = result.lastModified?.toFormattedString() ?: "",
|
lastModified = result.lastModified?.toFormattedString() ?: "",
|
||||||
entityTag = result.etag ?: "",
|
entityTag = result.etag ?: "",
|
||||||
statusCode = result.statusCode,
|
statusCode = result.statusCode,
|
||||||
file = file,
|
file = file
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,8 +203,8 @@ object RepositoryUpdater {
|
|||||||
Result.Error(
|
Result.Error(
|
||||||
UpdateException(
|
UpdateException(
|
||||||
errorType = errorType,
|
errorType = errorType,
|
||||||
message = "Failed with Status: ${result.statusCode}",
|
message = "Failed with Status: ${result.statusCode}"
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +228,7 @@ object RepositoryUpdater {
|
|||||||
mergerFile: File = Cache.getTemporaryFile(context),
|
mergerFile: File = Cache.getTemporaryFile(context),
|
||||||
lastModified: String,
|
lastModified: String,
|
||||||
entityTag: String,
|
entityTag: String,
|
||||||
callback: (Stage, Long, Long?) -> Unit,
|
callback: (Stage, Long, Long?) -> Unit
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var rollback = true
|
var rollback = true
|
||||||
return synchronized(updaterLock) {
|
return synchronized(updaterLock) {
|
||||||
@@ -261,7 +258,7 @@ object RepositoryUpdater {
|
|||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
version: Int,
|
version: Int,
|
||||||
timestamp: Long,
|
timestamp: Long
|
||||||
) {
|
) {
|
||||||
changedRepository = repository.update(
|
changedRepository = repository.update(
|
||||||
mirrors,
|
mirrors,
|
||||||
@@ -270,7 +267,7 @@ object RepositoryUpdater {
|
|||||||
version,
|
version,
|
||||||
lastModified,
|
lastModified,
|
||||||
entityTag,
|
entityTag,
|
||||||
timestamp,
|
timestamp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +284,7 @@ object RepositoryUpdater {
|
|||||||
|
|
||||||
override fun onReleases(
|
override fun onReleases(
|
||||||
packageName: String,
|
packageName: String,
|
||||||
releases: List<Release>,
|
releases: List<Release>
|
||||||
) {
|
) {
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
throw InterruptedException()
|
throw InterruptedException()
|
||||||
@@ -298,7 +295,7 @@ object RepositoryUpdater {
|
|||||||
unmergedReleases.clear()
|
unmergedReleases.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
@@ -321,11 +318,11 @@ object RepositoryUpdater {
|
|||||||
callback(
|
callback(
|
||||||
Stage.MERGE,
|
Stage.MERGE,
|
||||||
progress.toLong(),
|
progress.toLong(),
|
||||||
totalCount.toLong(),
|
totalCount.toLong()
|
||||||
)
|
)
|
||||||
Database.UpdaterAdapter.putTemporary(
|
Database.UpdaterAdapter.putTemporary(
|
||||||
products
|
products
|
||||||
.map { transformProduct(it, features, unstable) },
|
.map { transformProduct(it, features, unstable) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,7 +336,7 @@ object RepositoryUpdater {
|
|||||||
throw UpdateException(
|
throw UpdateException(
|
||||||
ErrorType.VALIDATION,
|
ErrorType.VALIDATION,
|
||||||
"New index is older than current index:" +
|
"New index is older than current index:" +
|
||||||
" ${workRepository.timestamp} < ${repository.timestamp}",
|
" ${workRepository.timestamp} < ${repository.timestamp}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,13 +349,13 @@ object RepositoryUpdater {
|
|||||||
|
|
||||||
val commitRepository = if (!workRepository.fingerprint.equals(
|
val commitRepository = if (!workRepository.fingerprint.equals(
|
||||||
fingerprint,
|
fingerprint,
|
||||||
ignoreCase = true,
|
ignoreCase = true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (workRepository.fingerprint.isNotEmpty()) {
|
if (workRepository.fingerprint.isNotEmpty()) {
|
||||||
throw UpdateException(
|
throw UpdateException(
|
||||||
ErrorType.VALIDATION,
|
ErrorType.VALIDATION,
|
||||||
"Certificate fingerprints do not match",
|
"Certificate fingerprints do not match"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +391,7 @@ object RepositoryUpdater {
|
|||||||
get() = codeSigners?.singleOrNull()
|
get() = codeSigners?.singleOrNull()
|
||||||
?: throw UpdateException(
|
?: throw UpdateException(
|
||||||
ErrorType.VALIDATION,
|
ErrorType.VALIDATION,
|
||||||
"index.jar must be signed by a single code signer",
|
"index.jar must be signed by a single code signer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@get:Throws(UpdateException::class)
|
@get:Throws(UpdateException::class)
|
||||||
@@ -402,13 +399,13 @@ object RepositoryUpdater {
|
|||||||
get() = signerCertPath?.certificates?.singleOrNull()
|
get() = signerCertPath?.certificates?.singleOrNull()
|
||||||
?: throw UpdateException(
|
?: throw UpdateException(
|
||||||
ErrorType.VALIDATION,
|
ErrorType.VALIDATION,
|
||||||
"index.jar code signer should have only one certificate",
|
"index.jar code signer should have only one certificate"
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun transformProduct(
|
private fun transformProduct(
|
||||||
product: Product,
|
product: Product,
|
||||||
features: Set<String>,
|
features: Set<String>,
|
||||||
unstable: Boolean,
|
unstable: Boolean
|
||||||
): Product {
|
): Product {
|
||||||
val releasePairs = product.releases
|
val releasePairs = product.releases
|
||||||
.distinctBy { it.identifier }
|
.distinctBy { it.identifier }
|
||||||
@@ -448,7 +445,7 @@ object RepositoryUpdater {
|
|||||||
selected = firstSelected?.let {
|
selected = firstSelected?.let {
|
||||||
it.first.versionCode == release.versionCode &&
|
it.first.versionCode == release.versionCode &&
|
||||||
it.second == incompatibilities
|
it.second == incompatibilities
|
||||||
} ?: false,
|
} ?: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return product.copy(releases = releases)
|
return product.copy(releases = releases)
|
||||||
@@ -460,5 +457,5 @@ data class IndexFile(
|
|||||||
val lastModified: String,
|
val lastModified: String,
|
||||||
val entityTag: String,
|
val entityTag: String,
|
||||||
val statusCode: Int,
|
val statusCode: Int,
|
||||||
val file: File,
|
val file: File
|
||||||
)
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user