v0.3.2-SNAPSHOT: added android-specific code and implementation, including defaultNamedStorage()
This commit is contained in:
parent
1a46c8cab3
commit
972b033be1
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
||||
.kotlin
|
||||
/.gigaide/gigaide.properties
|
||||
/java_pid40366.hprof
|
||||
/local.properties
|
||||
|
||||
@ -4,13 +4,15 @@ plugins {
|
||||
kotlin("multiplatform") version "2.2.21"
|
||||
kotlin("plugin.serialization") version "2.2.21"
|
||||
id("org.jetbrains.dokka") version "1.9.20"
|
||||
id("com.android.library") version "8.7.2"
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven("https://maven.universablockchain.com/")
|
||||
@ -19,6 +21,10 @@ repositories {
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
jvm()
|
||||
androidTarget() {
|
||||
// Ensure Android variant is published to Maven so consumers get androidMain APIs
|
||||
publishLibraryVariants("release")
|
||||
}
|
||||
js {
|
||||
browser()
|
||||
nodejs()
|
||||
@ -57,7 +63,7 @@ kotlin {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
// this is actually a bug: we need only the core, but bare core causes strange errors
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
api("net.sergeych:mp_stools:[1.5.2,)")
|
||||
api("net.sergeych:mp_stools:[1.6.3,)")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
|
||||
}
|
||||
}
|
||||
@ -82,6 +88,11 @@ kotlin {
|
||||
}
|
||||
val jvmMain by getting
|
||||
val jvmTest by getting
|
||||
val androidMain by getting {
|
||||
dependencies {
|
||||
// Android base64 and preferences are in the SDK; no extra deps required
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
}
|
||||
@ -119,6 +130,26 @@ publishing {
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.sergeych.bintools"
|
||||
compileSdk = 34
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
// Publish only release variant of the Android target so consumers (Android apps)
|
||||
// resolve the platform artifact instead of common metadata, which is required
|
||||
// to access Android-only APIs like net.sergeych.bintools.AndroidKV
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.dokkaHtml.configure {
|
||||
outputDirectory.set(buildDir.resolve("dokka"))
|
||||
dokkaSourceSets {
|
||||
|
||||
@ -1,3 +1,21 @@
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven("https://maven.universablockchain.com/")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "mp_bintools"
|
||||
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
package net.sergeych.bintools
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import net.sergeych.bintools.AndroidKV.init
|
||||
import net.sergeych.mp_tools.decodeBase64Compact
|
||||
import net.sergeych.mp_tools.encodeToBase64Compact
|
||||
|
||||
/**
|
||||
* Simple holder to provide Application [Context] for this library.
|
||||
*
|
||||
* Call [init] from your app (e.g., in `Application.onCreate`) before using
|
||||
* [defaultNamedStorage].
|
||||
*/
|
||||
object AndroidKV {
|
||||
@Volatile
|
||||
private var appContext: Context? = null
|
||||
|
||||
fun init(context: Context) {
|
||||
appContext = context.applicationContext
|
||||
}
|
||||
|
||||
internal fun requireContext(): Context =
|
||||
appContext ?: error("AndroidKV is not initialized. Call AndroidKV.init(applicationContext) first.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of [KVStorage] backed by [SharedPreferences].
|
||||
* Values are stored as base64-encoded strings to fit preferences capabilities.
|
||||
*/
|
||||
class SharedPreferencesKVStorage(
|
||||
private val prefs: SharedPreferences,
|
||||
keyPrefix: String = ""
|
||||
) : KVStorage {
|
||||
private val prefix = if (keyPrefix.isEmpty()) "" else "$keyPrefix:"
|
||||
|
||||
private fun k(key: String) = "$prefix$key"
|
||||
|
||||
override fun get(key: String): ByteArray? =
|
||||
prefs.getString(k(key), null)?.decodeBase64Compact()
|
||||
|
||||
override fun set(key: String, value: ByteArray?) {
|
||||
val e = prefs.edit()
|
||||
val kk = k(key)
|
||||
if (value == null) e.remove(kk)
|
||||
else e.putString(kk, value.encodeToBase64Compact())
|
||||
e.apply()
|
||||
}
|
||||
|
||||
override val keys: Set<String>
|
||||
get() {
|
||||
val allKeys = prefs.all.keys
|
||||
if (prefix.isEmpty()) return allKeys
|
||||
return allKeys.filter { it.startsWith(prefix) }
|
||||
.map { it.removePrefix(prefix) }
|
||||
.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
actual fun defaultNamedStorage(name: String): KVStorage {
|
||||
val ctx = AndroidKV.requireContext()
|
||||
val prefs = ctx.getSharedPreferences(name, Context.MODE_PRIVATE)
|
||||
// We use separate preferences file per name, so prefix can be empty
|
||||
return SharedPreferencesKVStorage(prefs)
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package net.sergeych.synctools
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* Android actual implementation mirrors JVM using [ReentrantLock].
|
||||
*/
|
||||
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
|
||||
private val access = ReentrantLock()
|
||||
override fun lock() {
|
||||
access.lock()
|
||||
}
|
||||
|
||||
override fun unlock() {
|
||||
access.unlock()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package net.sergeych.synctools
|
||||
|
||||
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
|
||||
actual class WaitHandle {
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
private val access = Object()
|
||||
|
||||
actual fun await(milliseconds: Long): Boolean {
|
||||
return synchronized(access) {
|
||||
try {
|
||||
access.wait(milliseconds)
|
||||
true
|
||||
} catch (_: InterruptedException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actual fun wakeUp() {
|
||||
synchronized(access) { access.notifyAll() }
|
||||
}
|
||||
}
|
||||
@ -218,7 +218,19 @@ class MemoryKVStorage(copyFrom: KVStorage? = null) : KVStorage {
|
||||
* - otherwise, the folder will be created in "`~/.local_storage`" parent directory
|
||||
* (which also will be created if needed).
|
||||
*
|
||||
* - For the native platorms it is not yet implemented (but will be soon).
|
||||
* - In Android (new!) it is implemented using preferences, but initializing is needed in
|
||||
* main activity `onCreate`, somewhat like:
|
||||
* ```kotlin
|
||||
* override fun onCreate(savedInstanceState: Bundle?) {
|
||||
* super.onCreate(savedInstanceState)
|
||||
* // set the Context which Preferences will be used for
|
||||
* // defaultNamedStorage
|
||||
* net.sergeych.bintools.AndroidKV.init(this)
|
||||
* }
|
||||
* ```
|
||||
* see also `AndroidKV` and `SharedPreferencesKVStorage` for Android
|
||||
*
|
||||
* - For the native platforms it is not yet implemented (but will be soon).
|
||||
*
|
||||
* See [DataKVStorage] and [DataProvider] to implement a KVStorage on filesystems and like,
|
||||
* and `FileDataProvider` class on JVM target.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user