Compare commits

...

2 Commits

15 changed files with 201 additions and 40 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@
.kotlin
/.gigaide/gigaide.properties
/java_pid40366.hprof
/local.properties

View File

@ -4,22 +4,11 @@ Multiplatform binary tools collection, including portable serialization of the c
many useful tools to work with binary data, like CRC family checksums, dumps, etc. It works well also in the browser and
in native targets.
# Recent changes
# Important note
- 0.1.12 published on all platforms. many small additions.
Currently published version 0.3.0 for all platform is fully compatible with breaking kotlinx.datetime/kotlin.time migration as of Kotlin 2.2.21 and is __a recommended version to use__.
- 0.1.11 added interesting collection classes: auto-sorted list with comparator, expirable cache, etc.
- 0.1.7 built with kotlin 2.0.20 which contains important fix in wasmJS
- 0.1.6 add many useful features, added support to wasmJS and all other platforms. Note to wasmJS: it appears to be a bug in wasm compiler so BipackDecoder could cause wasm loading problem.
- 0.1.1: added serialized KVStorage with handy implementation on JVM and JS platforms and some required synchronization
tools.
-
- 0.1.0: uses modern kotlin 1.9.*, fixes problem with singleton or empty/object serialization
The last 1.8-based version is 0.0.8. Some fixes are not yet backported to it pls leave an issue of needed.
Sorry for inconveniences, it is all caused by strange ideas of the Kotlin team.
# Documentation

View File

@ -1,34 +1,40 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
kotlin("multiplatform") version "2.2.20"
kotlin("plugin.serialization") version "2.2.20"
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.2.1-SNAPSHOT"
version = "0.3.2-SNAPSHOT"
repositories {
google()
mavenCentral()
mavenLocal()
maven("https://maven.universablockchain.com/")
}
kotlin {
jvmToolchain(8)
jvmToolchain(17)
jvm()
androidTarget() {
// Ensure Android variant is published to Maven so consumers get androidMain APIs
publishLibraryVariants("release")
}
js {
browser()
nodejs()
}
// macosArm64()
// iosX64()
// iosArm64()
// macosX64()
// iosSimulatorArm64()
macosArm64()
iosX64()
iosArm64()
macosX64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
@ -50,14 +56,15 @@ kotlin {
languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
languageSettings.optIn("kotlin.time.ExperimentalTime")
}
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
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.7.3")
api("net.sergeych:mp_stools:[1.5.2,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
api("net.sergeych:mp_stools:[1.6.3,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.7.1")
}
}
val nativeMain by creating {
@ -81,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 {
}
@ -118,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 {

View File

@ -1,3 +1,8 @@
kotlin.code.style=official
kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.daemon.jvmargs=-Xmx3072M
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true
org.gradle.caching=true

View File

@ -1,3 +1,21 @@
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
mavenLocal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
mavenLocal()
maven("https://maven.universablockchain.com/")
}
}
rootProject.name = "mp_bintools"

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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() }
}
}

View File

@ -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.

View File

@ -1,6 +1,5 @@
package net.sergeych.bipack
import kotlinx.datetime.Instant
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
@ -12,6 +11,7 @@ import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer
import net.sergeych.bintools.*
import kotlin.time.Instant
/**
* Decode BiPack format. Note that it relies on [DataSource] so can throw [DataSource.EndOfData]
@ -74,7 +74,7 @@ class BipackDecoder(
}
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return if (deserializer == Instant.serializer())
return if (deserializer == serializer<Instant>())
Instant.fromEpochMilliseconds(decodeLong()) as T
else
super.decodeSerializableValue(deserializer)

View File

@ -1,6 +1,5 @@
package net.sergeych.bipack
import kotlinx.datetime.Instant
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.AbstractEncoder
@ -9,6 +8,7 @@ import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.serializer
import net.sergeych.bintools.*
import kotlin.time.Instant
class BipackEncoder(val output: DataSink) : AbstractEncoder() {

View File

@ -2,11 +2,11 @@ package net.sergeych.collections
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import net.sergeych.mptools.withReentrantLock
import kotlin.time.Clock
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Instant
/**
* MRU cache with expiration, with safe async concurrent access.

View File

@ -1,8 +1,8 @@
package net.sergecyh.diwan.tools
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlin.time.Clock
import kotlin.time.Duration
import kotlin.time.Instant
/**
* Value with expiration.

View File

@ -1,8 +1,8 @@
package net.sergecyh.diwan.tools
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlin.time.Clock
import kotlin.time.Duration
import kotlin.time.Instant
/**
* Experimental.

View File

@ -1,7 +1,5 @@
package bipack
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.sergeych.bintools.encodeToHex
@ -13,6 +11,8 @@ import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.time.Clock
import kotlin.time.Instant
@Serializable
data class Foobar1N(val bar: Int, val foo: Int = 117)