mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-06-20 22:29:19 +00:00
Initial Commit
This commit is contained in:
179
src/main/kotlin/com/looker/droidify/content/Cache.kt
Normal file
179
src/main/kotlin/com/looker/droidify/content/Cache.kt
Normal file
@ -0,0 +1,179 @@
|
||||
package com.looker.droidify.content
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
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.looker.droidify.utility.extension.android.*
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subPath(dir: File, file: File): String {
|
||||
val dirPath = "${dir.path}/"
|
||||
val filePath = file.path
|
||||
filePath.startsWith(dirPath) || throw RuntimeException()
|
||||
return filePath.substring(dirPath.length)
|
||||
}
|
||||
|
||||
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 getReleaseUri(context: Context, cacheFileName: String): Uri {
|
||||
val file = getReleaseFile(context, cacheFileName)
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PROVIDERS)
|
||||
val authority = packageInfo.providers.find { it.name == Provider::class.java.name }!!.authority
|
||||
return Uri.Builder().scheme("content").authority(authority)
|
||||
.encodedPath(subPath(context.cacheDir, file)).build()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
151
src/main/kotlin/com/looker/droidify/content/Preferences.kt
Normal file
151
src/main/kotlin/com/looker/droidify/content/Preferences.kt
Normal file
@ -0,0 +1,151 @@
|
||||
package com.looker.droidify.content
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import com.looker.droidify.R
|
||||
import com.looker.droidify.entity.ProductItem
|
||||
import com.looker.droidify.utility.extension.android.*
|
||||
import java.net.Proxy
|
||||
|
||||
object Preferences {
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
private val subject = PublishSubject.create<Key<*>>()
|
||||
|
||||
private val keys = sequenceOf(Key.AutoSync, Key.IncompatibleVersions, Key.ProxyHost, Key.ProxyPort, Key.ProxyType,
|
||||
Key.SortOrder, Key.Theme, Key.UpdateNotify, Key.UpdateUnstable).map { Pair(it.name, it) }.toMap()
|
||||
|
||||
fun init(context: Context) {
|
||||
preferences = context.getSharedPreferences("${context.packageName}_preferences", Context.MODE_PRIVATE)
|
||||
preferences.registerOnSharedPreferenceChangeListener { _, keyString -> keys[keyString]?.let(subject::onNext) }
|
||||
}
|
||||
|
||||
val observable: Observable<Key<*>>
|
||||
get() = subject
|
||||
|
||||
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 AutoSync: Key<Preferences.AutoSync>("auto_sync", Value.EnumerationValue(Preferences.AutoSync.Wifi))
|
||||
object IncompatibleVersions: Key<Boolean>("incompatible_versions", Value.BooleanValue(false))
|
||||
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 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 UpdateNotify: Key<Boolean>("update_notify", Value.BooleanValue(true))
|
||||
object UpdateUnstable: Key<Boolean>("update_unstable", Value.BooleanValue(false))
|
||||
}
|
||||
|
||||
sealed class AutoSync(override val valueString: String): Enumeration<AutoSync> {
|
||||
override val values: List<AutoSync>
|
||||
get() = listOf(Never, Wifi, Always)
|
||||
|
||||
object Never: AutoSync("never")
|
||||
object Wifi: AutoSync("wifi")
|
||||
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: ProductItem.Order): Enumeration<SortOrder> {
|
||||
override val values: List<SortOrder>
|
||||
get() = listOf(Name, Added, Update)
|
||||
|
||||
object Name: SortOrder("name", ProductItem.Order.NAME)
|
||||
object Added: SortOrder("added", ProductItem.Order.DATE_ADDED)
|
||||
object Update: SortOrder("update", ProductItem.Order.LAST_UPDATE)
|
||||
}
|
||||
|
||||
sealed class Theme(override val valueString: String): Enumeration<Theme> {
|
||||
override val values: List<Theme>
|
||||
get() = if (Android.sdk(29)) listOf(System, Light, Dark) else listOf(Light, Dark)
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.looker.droidify.content
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import com.looker.droidify.database.Database
|
||||
import com.looker.droidify.entity.ProductPreference
|
||||
import com.looker.droidify.utility.extension.json.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.charset.Charset
|
||||
|
||||
object ProductPreferences {
|
||||
private val defaultProductPreference = ProductPreference(false, 0L)
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private val subject = PublishSubject.create<Pair<String, Long?>>()
|
||||
|
||||
fun init(context: Context) {
|
||||
preferences = context.getSharedPreferences("product_preferences", Context.MODE_PRIVATE)
|
||||
Database.LockAdapter.putAll(preferences.all.keys
|
||||
.mapNotNull { packageName -> this[packageName].databaseVersionCode?.let { Pair(packageName, it) } })
|
||||
subject
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe { (packageName, versionCode) ->
|
||||
if (versionCode != null) {
|
||||
Database.LockAdapter.put(Pair(packageName, versionCode))
|
||||
} else {
|
||||
Database.LockAdapter.delete(packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ProductPreference.databaseVersionCode: Long?
|
||||
get() = when {
|
||||
ignoreUpdates -> 0L
|
||||
ignoreVersionCode > 0L -> ignoreVersionCode
|
||||
else -> null
|
||||
}
|
||||
|
||||
operator fun get(packageName: String): ProductPreference {
|
||||
return if (preferences.contains(packageName)) {
|
||||
try {
|
||||
Json.factory.createParser(preferences.getString(packageName, "{}"))
|
||||
.use { it.parseDictionary(ProductPreference.Companion::deserialize) }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
defaultProductPreference
|
||||
}
|
||||
} else {
|
||||
defaultProductPreference
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(packageName: String, productPreference: ProductPreference) {
|
||||
val oldProductPreference = this[packageName]
|
||||
preferences.edit().putString(packageName, ByteArrayOutputStream()
|
||||
.apply { Json.factory.createGenerator(this).use { it.writeDictionary(productPreference::serialize) } }
|
||||
.toByteArray().toString(Charset.defaultCharset())).apply()
|
||||
if (oldProductPreference.ignoreUpdates != productPreference.ignoreUpdates ||
|
||||
oldProductPreference.ignoreVersionCode != productPreference.ignoreVersionCode) {
|
||||
subject.onNext(Pair(packageName, productPreference.databaseVersionCode))
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user