mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-06-20 06:09:19 +00:00
Rename package to com.machaiv3lli.fdroid
This commit is contained in:
183
src/main/kotlin/com/machiav3lli/fdroid/content/Cache.kt
Normal file
183
src/main/kotlin/com/machiav3lli/fdroid/content/Cache.kt
Normal file
@ -0,0 +1,183 @@
|
||||
package com.machiav3lli.fdroid.content
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.OpenableColumns
|
||||
import android.system.Os
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
object Cache {
|
||||
private fun ensureCacheDir(context: Context, name: String): File {
|
||||
return File(
|
||||
context.cacheDir,
|
||||
name
|
||||
).apply { isDirectory || mkdirs() || throw RuntimeException() }
|
||||
}
|
||||
|
||||
private fun applyOrMode(file: File, mode: Int) {
|
||||
val oldMode = Os.stat(file.path).st_mode and 0b111111111111
|
||||
val newMode = oldMode or mode
|
||||
if (newMode != oldMode) {
|
||||
Os.chmod(file.path, newMode)
|
||||
}
|
||||
}
|
||||
|
||||
fun getImagesDir(context: Context): File {
|
||||
return ensureCacheDir(context, "images")
|
||||
}
|
||||
|
||||
fun getPartialReleaseFile(context: Context, cacheFileName: String): File {
|
||||
return File(ensureCacheDir(context, "partial"), cacheFileName)
|
||||
}
|
||||
|
||||
fun getReleaseFile(context: Context, cacheFileName: String): File {
|
||||
return File(ensureCacheDir(context, "releases"), cacheFileName).apply {
|
||||
if (!Android.sdk(24)) {
|
||||
// Make readable for package installer
|
||||
val cacheDir = context.cacheDir.parentFile!!.parentFile!!
|
||||
generateSequence(this) { it.parentFile!! }.takeWhile { it != cacheDir }.forEach {
|
||||
when {
|
||||
it.isDirectory -> applyOrMode(it, 0b001001001)
|
||||
it.isFile -> applyOrMode(it, 0b100100100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getTemporaryFile(context: Context): File {
|
||||
return File(ensureCacheDir(context, "temporary"), UUID.randomUUID().toString())
|
||||
}
|
||||
|
||||
fun cleanup(context: Context) {
|
||||
thread {
|
||||
cleanup(
|
||||
context,
|
||||
Pair("images", 0),
|
||||
Pair("partial", 24),
|
||||
Pair("releases", 24),
|
||||
Pair("temporary", 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanup(context: Context, vararg dirHours: Pair<String, Int>) {
|
||||
val knownNames = dirHours.asSequence().map { it.first }.toSet()
|
||||
val files = context.cacheDir.listFiles().orEmpty()
|
||||
files.asSequence().filter { it.name !in knownNames }.forEach {
|
||||
if (it.isDirectory) {
|
||||
cleanupDir(it, 0)
|
||||
it.delete()
|
||||
} else {
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
dirHours.forEach { (name, hours) ->
|
||||
if (hours > 0) {
|
||||
val file = File(context.cacheDir, name)
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory) {
|
||||
cleanupDir(file, hours)
|
||||
} else {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupDir(dir: File, hours: Int) {
|
||||
dir.listFiles()?.forEach {
|
||||
val older = hours <= 0 || run {
|
||||
val olderThan = System.currentTimeMillis() / 1000L - hours * 60 * 60
|
||||
try {
|
||||
val stat = Os.lstat(it.path)
|
||||
stat.st_atime < olderThan
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
if (older) {
|
||||
if (it.isDirectory) {
|
||||
cleanupDir(it, hours)
|
||||
if (it.isDirectory) {
|
||||
it.delete()
|
||||
}
|
||||
} else {
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Provider : ContentProvider() {
|
||||
companion object {
|
||||
private val defaultColumns = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
|
||||
}
|
||||
|
||||
private fun getFileAndTypeForUri(uri: Uri): Pair<File, String> {
|
||||
return when (uri.pathSegments?.firstOrNull()) {
|
||||
"releases" -> Pair(
|
||||
File(context!!.cacheDir, uri.encodedPath!!),
|
||||
"application/vnd.android.package-archive"
|
||||
)
|
||||
else -> throw SecurityException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
override fun query(
|
||||
uri: Uri, projection: Array<String>?,
|
||||
selection: String?, selectionArgs: Array<out String>?, sortOrder: String?,
|
||||
): Cursor {
|
||||
val file = getFileAndTypeForUri(uri).first
|
||||
val columns = (projection ?: defaultColumns).mapNotNull {
|
||||
when (it) {
|
||||
OpenableColumns.DISPLAY_NAME -> Pair(it, file.name)
|
||||
OpenableColumns.SIZE -> Pair(it, file.length())
|
||||
else -> null
|
||||
}
|
||||
}.unzip()
|
||||
return MatrixCursor(columns.first.toTypedArray()).apply { addRow(columns.second.toTypedArray()) }
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String = getFileAndTypeForUri(uri).second
|
||||
|
||||
private val unsupported: Nothing
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri = unsupported
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
|
||||
unsupported
|
||||
|
||||
override fun update(
|
||||
uri: Uri, contentValues: ContentValues?,
|
||||
selection: String?, selectionArgs: Array<out String>?,
|
||||
): Int = unsupported
|
||||
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
val openMode = when (mode) {
|
||||
"r" -> ParcelFileDescriptor.MODE_READ_ONLY
|
||||
"w", "wt" -> ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or
|
||||
ParcelFileDescriptor.MODE_TRUNCATE
|
||||
"wa" -> ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or
|
||||
ParcelFileDescriptor.MODE_APPEND
|
||||
"rw" -> ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE
|
||||
"rwt" -> ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE or
|
||||
ParcelFileDescriptor.MODE_TRUNCATE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
val file = getFileAndTypeForUri(uri).first
|
||||
return ParcelFileDescriptor.open(file, openMode)
|
||||
}
|
||||
}
|
||||
}
|
284
src/main/kotlin/com/machiav3lli/fdroid/content/Preferences.kt
Normal file
284
src/main/kotlin/com/machiav3lli/fdroid/content/Preferences.kt
Normal file
@ -0,0 +1,284 @@
|
||||
package com.machiav3lli.fdroid.content
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import com.machiav3lli.fdroid.PREFS_LANGUAGE
|
||||
import com.machiav3lli.fdroid.PREFS_LANGUAGE_DEFAULT
|
||||
import com.machiav3lli.fdroid.R
|
||||
import com.machiav3lli.fdroid.entity.Order
|
||||
import com.machiav3lli.fdroid.utility.extension.android.Android
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.Proxy
|
||||
|
||||
object Preferences {
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
private val mutableSubject = MutableSharedFlow<Key<*>>()
|
||||
val subject = mutableSubject.asSharedFlow()
|
||||
|
||||
private val keys = sequenceOf(
|
||||
Key.Language,
|
||||
Key.AutoSync,
|
||||
Key.InstallAfterSync,
|
||||
Key.IncompatibleVersions,
|
||||
Key.ListAnimation,
|
||||
Key.ShowScreenshots,
|
||||
Key.UpdatedApps,
|
||||
Key.NewApps,
|
||||
Key.ProxyHost,
|
||||
Key.ProxyPort,
|
||||
Key.ProxyType,
|
||||
Key.RootPermission,
|
||||
Key.RootSessionInstaller,
|
||||
Key.SortOrder,
|
||||
Key.Theme,
|
||||
Key.DefaultTab,
|
||||
Key.UpdateNotify,
|
||||
Key.UpdateUnstable,
|
||||
Key.IgnoreIgnoreBatteryOptimization
|
||||
).map { Pair(it.name, it) }.toMap()
|
||||
|
||||
fun init(context: Context) {
|
||||
preferences =
|
||||
context.getSharedPreferences(
|
||||
"${context.packageName}_preferences",
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
preferences.registerOnSharedPreferenceChangeListener { _, keyString ->
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
keys[keyString]?.let {
|
||||
mutableSubject.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Value<T> {
|
||||
abstract val value: T
|
||||
|
||||
internal abstract fun get(
|
||||
preferences: SharedPreferences,
|
||||
key: String,
|
||||
defaultValue: Value<T>,
|
||||
): T
|
||||
|
||||
internal abstract fun set(preferences: SharedPreferences, key: String, value: T)
|
||||
|
||||
class BooleanValue(override val value: Boolean) : Value<Boolean>() {
|
||||
override fun get(
|
||||
preferences: SharedPreferences,
|
||||
key: String,
|
||||
defaultValue: Value<Boolean>,
|
||||
): Boolean {
|
||||
return preferences.getBoolean(key, defaultValue.value)
|
||||
}
|
||||
|
||||
override fun set(preferences: SharedPreferences, key: String, value: Boolean) {
|
||||
preferences.edit().putBoolean(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
class IntValue(override val value: Int) : Value<Int>() {
|
||||
override fun get(
|
||||
preferences: SharedPreferences,
|
||||
key: String,
|
||||
defaultValue: Value<Int>,
|
||||
): Int {
|
||||
return preferences.getInt(key, defaultValue.value)
|
||||
}
|
||||
|
||||
override fun set(preferences: SharedPreferences, key: String, value: Int) {
|
||||
preferences.edit().putInt(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
class StringValue(override val value: String) : Value<String>() {
|
||||
override fun get(
|
||||
preferences: SharedPreferences,
|
||||
key: String,
|
||||
defaultValue: Value<String>,
|
||||
): String {
|
||||
return preferences.getString(key, defaultValue.value) ?: defaultValue.value
|
||||
}
|
||||
|
||||
override fun set(preferences: SharedPreferences, key: String, value: String) {
|
||||
preferences.edit().putString(key, value).apply()
|
||||
}
|
||||
}
|
||||
|
||||
class EnumerationValue<T : Enumeration<T>>(override val value: T) : Value<T>() {
|
||||
override fun get(
|
||||
preferences: SharedPreferences,
|
||||
key: String,
|
||||
defaultValue: Value<T>,
|
||||
): T {
|
||||
val value = preferences.getString(key, defaultValue.value.valueString)
|
||||
return defaultValue.value.values.find { it.valueString == value }
|
||||
?: defaultValue.value
|
||||
}
|
||||
|
||||
override fun set(preferences: SharedPreferences, key: String, value: T) {
|
||||
preferences.edit().putString(key, value.valueString).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Enumeration<T> {
|
||||
val values: List<T>
|
||||
val valueString: String
|
||||
}
|
||||
|
||||
sealed class Key<T>(val name: String, val default: Value<T>) {
|
||||
object Language : Key<String>(PREFS_LANGUAGE, Value.StringValue(PREFS_LANGUAGE_DEFAULT))
|
||||
object AutoSync : Key<Preferences.AutoSync>(
|
||||
"auto_sync",
|
||||
Value.EnumerationValue(Preferences.AutoSync.Wifi)
|
||||
)
|
||||
|
||||
object InstallAfterSync :
|
||||
Key<Boolean>("auto_sync_install", Value.BooleanValue(Android.sdk(31)))
|
||||
|
||||
object IncompatibleVersions :
|
||||
Key<Boolean>("incompatible_versions", Value.BooleanValue(false))
|
||||
|
||||
object ListAnimation :
|
||||
Key<Boolean>("list_animation", Value.BooleanValue(false))
|
||||
|
||||
object ShowScreenshots :
|
||||
Key<Boolean>("show_screenshots", Value.BooleanValue(true))
|
||||
|
||||
object UpdatedApps : Key<Int>("updated_apps", Value.IntValue(100))
|
||||
object NewApps : Key<Int>("new_apps", Value.IntValue(20))
|
||||
|
||||
object ProxyHost : Key<String>("proxy_host", Value.StringValue("localhost"))
|
||||
object ProxyPort : Key<Int>("proxy_port", Value.IntValue(9050))
|
||||
object ProxyType : Key<Preferences.ProxyType>(
|
||||
"proxy_type",
|
||||
Value.EnumerationValue(Preferences.ProxyType.Direct)
|
||||
)
|
||||
|
||||
object RootPermission : Key<Boolean>("root_permission", Value.BooleanValue(false))
|
||||
object RootSessionInstaller :
|
||||
Key<Boolean>("root_session_installer", Value.BooleanValue(false))
|
||||
|
||||
object SortOrder : Key<Preferences.SortOrder>(
|
||||
"sort_order",
|
||||
Value.EnumerationValue(Preferences.SortOrder.Update)
|
||||
)
|
||||
|
||||
object Theme : Key<Preferences.Theme>(
|
||||
"theme", Value.EnumerationValue(
|
||||
if (Android.sdk(29))
|
||||
Preferences.Theme.System else Preferences.Theme.Light
|
||||
)
|
||||
)
|
||||
|
||||
object DefaultTab : Key<Preferences.DefaultTab>(
|
||||
"default_tab", Value.EnumerationValue(
|
||||
Preferences.DefaultTab.Explore
|
||||
)
|
||||
)
|
||||
|
||||
object UpdateNotify : Key<Boolean>("update_notify", Value.BooleanValue(true))
|
||||
object UpdateUnstable : Key<Boolean>("update_unstable", Value.BooleanValue(false))
|
||||
|
||||
object IgnoreIgnoreBatteryOptimization :
|
||||
Key<Boolean>("ignore_ignore_battery_optimization", Value.BooleanValue(false))
|
||||
}
|
||||
|
||||
sealed class AutoSync(override val valueString: String) : Enumeration<AutoSync> {
|
||||
override val values: List<AutoSync>
|
||||
get() = listOf(Never, Wifi, WifiBattery, Always)
|
||||
|
||||
object Never : AutoSync("never")
|
||||
object Wifi : AutoSync("wifi")
|
||||
object WifiBattery : AutoSync("wifi-battery")
|
||||
object Always : AutoSync("always")
|
||||
}
|
||||
|
||||
sealed class ProxyType(override val valueString: String, val proxyType: Proxy.Type) :
|
||||
Enumeration<ProxyType> {
|
||||
override val values: List<ProxyType>
|
||||
get() = listOf(Direct, Http, Socks)
|
||||
|
||||
object Direct : ProxyType("direct", Proxy.Type.DIRECT)
|
||||
object Http : ProxyType("http", Proxy.Type.HTTP)
|
||||
object Socks : ProxyType("socks", Proxy.Type.SOCKS)
|
||||
}
|
||||
|
||||
sealed class SortOrder(override val valueString: String, val order: Order) :
|
||||
Enumeration<SortOrder> {
|
||||
override val values: List<SortOrder>
|
||||
get() = listOf(Name, Added, Update)
|
||||
|
||||
object Name : SortOrder("name", Order.NAME)
|
||||
object Added : SortOrder("added", Order.DATE_ADDED)
|
||||
object Update : SortOrder("update", Order.LAST_UPDATE)
|
||||
}
|
||||
|
||||
sealed class Theme(override val valueString: String) : Enumeration<Theme> {
|
||||
override val values: List<Theme>
|
||||
get() = if (Android.sdk(29)) listOf(System, AmoledSystem, Light, Dark, Amoled)
|
||||
else listOf(Light, Dark, Amoled)
|
||||
|
||||
abstract fun getResId(configuration: Configuration): Int
|
||||
|
||||
object System : Theme("system") {
|
||||
override fun getResId(configuration: Configuration): Int {
|
||||
return if ((configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) != 0)
|
||||
R.style.Theme_Main_Dark else R.style.Theme_Main_Light
|
||||
}
|
||||
}
|
||||
|
||||
object AmoledSystem : Theme("system-amoled") {
|
||||
override fun getResId(configuration: Configuration): Int {
|
||||
return if ((configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) != 0)
|
||||
R.style.Theme_Main_Amoled else R.style.Theme_Main_Light
|
||||
}
|
||||
}
|
||||
|
||||
object Light : Theme("light") {
|
||||
override fun getResId(configuration: Configuration): Int = R.style.Theme_Main_Light
|
||||
}
|
||||
|
||||
object Dark : Theme("dark") {
|
||||
override fun getResId(configuration: Configuration): Int = R.style.Theme_Main_Dark
|
||||
}
|
||||
|
||||
object Amoled : Theme("amoled") {
|
||||
override fun getResId(configuration: Configuration): Int = R.style.Theme_Main_Amoled
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DefaultTab(override val valueString: String) : Enumeration<DefaultTab> {
|
||||
override val values: List<DefaultTab>
|
||||
get() = listOf(Explore, Latest, Installed)
|
||||
|
||||
abstract fun getResId(configuration: Configuration): Int
|
||||
|
||||
object Explore : DefaultTab("explore") {
|
||||
override fun getResId(configuration: Configuration): Int = R.id.exploreTab
|
||||
}
|
||||
|
||||
object Latest : DefaultTab("latest") {
|
||||
override fun getResId(configuration: Configuration): Int = R.id.latestTab
|
||||
}
|
||||
|
||||
object Installed : DefaultTab("installed") {
|
||||
override fun getResId(configuration: Configuration): Int = R.id.installedTab
|
||||
}
|
||||
}
|
||||
|
||||
operator fun <T> get(key: Key<T>): T {
|
||||
return key.default.get(preferences, key.name, key.default)
|
||||
}
|
||||
|
||||
operator fun <T> set(key: Key<T>, value: T) {
|
||||
key.default.set(preferences, key.name, value)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user