Skip to content

Commit

Permalink
Merge pull request #11 from y9san9/master
Browse files Browse the repository at this point in the history
KDS 3.0
  • Loading branch information
y9vad9 committed May 26, 2021
2 parents 86f5c8f + e280b13 commit a7f30a4
Show file tree
Hide file tree
Showing 54 changed files with 749 additions and 652 deletions.
50 changes: 12 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,23 @@ We really want someone to use our library, ask us about any questions and give s

### Storage
```kotlin
object Storage : KDataStorage() { // or KDataStorage("name") or KDataStorage({ path("...") })
var launchesCount by property(0)
var list by property(mutableListOf<String>())
}
val storage = KFileDataStorage(name = "data.json")
// Or
val storage = KFileDataStorage(absolutePath = "...")

var launchesCount by storage.property { 0 }
var list by storage.property { mutableListOf<String>() }


suspend fun main() = Storage.commitMutate {
fun main() = storage.mutateBlocking {
list += "Element"
launchesCount++
}
```

### Value Storage
```kotlin
val storage = KValueStorage(0)
val launchesCount by storage

suspend fun main() = storage.commitMutate {
println("${++launchesCount}")
}
```
There are both blocking and asynchronous implementations (except JS-browser where there is only blocking implementation due to using `localStorage` instead of files).

## Non-coroutines way
There is also a way for changing the storage without suspend context in infinitely-running apps:
```kotlin
class KindaActivity : CoroutineScope by ... {
// Scope is still optional
object Storage : KDataStorage(scope = this) { // or KDataStorage("name") or KDataStorage({ path("...") })
var launchesCount by property(0)
var list by property(mutableListOf<String>())
}

fun onCreate() = with(Storage) {
// Commit launches automatically since there is delegate call
launchesCount++

// However, list is a mutable object, so explicit mutation declaration required
mutate {
list += "Element"
}
}
}
```
Library may be fully customized since you can implement your own [DataManager](src/commonMain/kotlin/fun/kotlingang/kds/manager/DataManager.kt)

## Installation
`$version` - library version, can be found in badge above
Expand All @@ -68,7 +42,7 @@ repositories {
}
}
dependencies {
implementation "fun.kotlingang.kds:kds:$version"
implementation "fun.kotlingang.kds:kds-{platformSuffix}:$version"
}
```
### Kotlin Gradle Dsl
Expand All @@ -77,7 +51,7 @@ repositories {
maven("https://maven.kotlingang.fun/")
}
dependencies {
implementation("fun.kotlingang.kds:kds:$version")
implementation("fun.kotlingang.kds:kds-{platformSuffix}:$version")
}
```
> For nodejs use `fun.kotlingang.kds:kds-node:$version` dependency
> `platformSuffix` (js, jvm, etc) is suffix required when you using the library not from common code, for nodejs use `node` suffix.
34 changes: 27 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,41 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(utils)
implementation(coroutines)
api(serialization)
}
}

val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val nodeMain by getting {
dependencies {
implementation(nodejsExternals)
}

val filesTarget by creating {
dependsOn(commonMain)
}

val jvmMain by getting {
dependsOn(filesTarget)
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}
val jsTest by getting {

val commonJsMain by creating {
dependsOn(commonMain)
}

val nodeMain by getting {
dependsOn(filesTarget)
dependsOn(commonJsMain)

dependencies {
implementation(kotlin("test-js"))
implementation(nodejsExternals)
}
}
val nodeTest by getting {
Expand All @@ -64,6 +75,15 @@ kotlin {
}
}

val jsMain by getting {
dependsOn(commonJsMain)
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}

all {
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
}
Expand Down
3 changes: 1 addition & 2 deletions buildSrc/src/main/kotlin/AppInfo.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
object AppInfo {
const val PACKAGE = "fun.kotlingang.kds"
const val VERSION = "2.6"
const val ARTIFACT_ID = "kds"
const val VERSION = "3.0.3"
const val NAME = "Kotlin Data Storage"
const val DESCRIPTION = "Multiplatform Coroutine-Based Kotlin Library for storing data via kotlinx.serialization"
}
3 changes: 0 additions & 3 deletions buildSrc/src/main/kotlin/Modules.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler


val KotlinDependencyHandler.utils get() = project(":utils")
2 changes: 0 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
rootProject.name = "kds"

include("utils")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package `fun`.kotlingang.kds.sync


internal actual inline fun <R> platformSynchronized(lock: Any, block: () -> R) = block()
8 changes: 8 additions & 0 deletions src/commonJsMain/kotlin/fun/kotlingang/kds/test/launchTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package `fun`.kotlingang.kds.test

import `fun`.kotlingang.kds.extensions.any.unit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch


actual fun CoroutineScope.launchTest(block: suspend CoroutineScope.() -> Unit) = launch { block() }.unit
54 changes: 54 additions & 0 deletions src/commonMain/kotlin/fun/kotlingang/kds/KAsyncDataStorage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@file:Suppress("MemberVisibilityCanBePrivate")

package `fun`.kotlingang.kds

import `fun`.kotlingang.kds.composition.AsyncCommitPerformer
import `fun`.kotlingang.kds.composition.AsyncCommittable
import `fun`.kotlingang.kds.manager.AsyncDataManager
import `fun`.kotlingang.kds.sync.platformSynchronized
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement


class KAsyncDataStorage @OptIn(DelicateCoroutinesApi::class) constructor (
json: Json = Json,
scope: CoroutineScope = GlobalScope + SupervisorJob() + CoroutineName("KDS Coroutine"),
private val manager: AsyncDataManager
) : KBlockingDataStorage(json, manager) {
private var asyncData: Map<String, JsonElement>? = null

override fun getOrLoadData() = platformSynchronized(lock = this) {
asyncData ?: manager.loadDataBlocking().parseData().also { asyncData = it }
}.toMutableMap()

private val loadDataMutex = Mutex()

/**
* The worst case may be when data will be loaded twice but it is not problem
* since library is recommended to use on small data.
* *It is impossible when using functions with their contacts.
*/
suspend fun loadData() = loadDataMutex.withLock {
if(asyncData != null) {
val data = manager.loadData().parseData()
platformSynchronized(lock = this) {
if (asyncData != null) {
asyncData = data
}
}
}
}

private val commitPerformer = AsyncCommitPerformer(scope) { manager.saveData(data.encodeData()) }

suspend fun commit() = commitPerformer.commit()
fun launchCommit() = commitPerformer.launchCommit()

override fun performAutoSave() {
if(autoSave)
launchCommit()
}
}
94 changes: 94 additions & 0 deletions src/commonMain/kotlin/fun/kotlingang/kds/KBlockingDataStorage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
@file:Suppress("MemberVisibilityCanBePrivate")

package `fun`.kotlingang.kds

import `fun`.kotlingang.kds.annotation.DelicateKDSApi
import `fun`.kotlingang.kds.composition.AutoSaveController
import `fun`.kotlingang.kds.composition.JsonReferencesProxy
import `fun`.kotlingang.kds.extensions.any.unit
import `fun`.kotlingang.kds.manager.BlockingDataManager
import `fun`.kotlingang.kds.mutate.*
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement


open class KBlockingDataStorage (
val json: Json = Json,
private val manager: BlockingDataManager
) {
@DelicateKDSApi
val autoSaveController = AutoSaveController()
@OptIn(DelicateKDSApi::class)
val autoSave by autoSaveController::autoSave

/**
* [mutate], [mutateBlocking], [mutateCommit] should be used instead
*/
@DelicateKDSApi
inline fun withoutSave(crossinline block: () -> Unit) {
autoSaveController.turnOff()
block()
autoSaveController.tryTurnOn()
}

protected open fun getOrLoadData() = manager.loadDataBlocking().parseData().toMutableMap()

private val dataProxy by lazy {
JsonReferencesProxy(json, getOrLoadData())
}

protected val data get() = dataProxy.publicData

fun loadDataBlocking() = dataProxy.unit

/**
* Stores all references to data in case references were changed
*/
@DelicateKDSApi
fun applyMutations() = dataProxy.applyMutations()

@DelicateKDSApi
fun <T> set(name: String, serializer: KSerializer<T>, value: T) {
dataProxy.set(name, serializer, value)
performAutoSave()
}

@DelicateKDSApi
inline fun <reified T> set(name: String, value: @Serializable T) =
set(name, json.serializersModule.serializer(), value)

@DelicateKDSApi
fun <T> get(name: String, serializer: KSerializer<T>, default: () -> T) =
dataProxy.get(name, serializer, default)

@DelicateKDSApi
inline fun <reified T> get(name: String, noinline default: () -> T) =
get(name, json.serializersModule.serializer(), default)

fun exists(name: String) = dataProxy.exists(name)

fun clear(name: String) {
dataProxy.clear(name)
performAutoSave()
}

fun clear() {
dataProxy.clear()
performAutoSave()
}

fun commitBlocking() = manager.saveDataBlocking(data.encodeData())

/**
* For [KBlockingDataStorage] auto save way is blocking way while
* for [KAsyncDataStorage] auto save way is async way
*/
protected open fun performAutoSave() {
if(autoSave)
commitBlocking()
}

protected fun String.parseData() = json.decodeFromString<Map<String, JsonElement>>(string = this)
protected fun Map<String, JsonElement>.encodeData() = json.encodeToString(value = this)
}
Loading

0 comments on commit a7f30a4

Please sign in to comment.