diff --git a/buildSrc/src/main/kotlin/AppInfo.kt b/buildSrc/src/main/kotlin/AppInfo.kt index 7b8d2a2..a6cd8d6 100644 --- a/buildSrc/src/main/kotlin/AppInfo.kt +++ b/buildSrc/src/main/kotlin/AppInfo.kt @@ -1,6 +1,6 @@ object AppInfo { const val PACKAGE = "fun.kotlingang.kds" - const val VERSION = "5.2.0" + const val VERSION = "5.2.1" const val NAME = "Kotlin Data Storage" const val DESCRIPTION = "Multiplatform Coroutine-Based Kotlin Library for storing data via kotlinx.serialization" } diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/annotation/ExperimentalKDSApi.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/annotation/ExperimentalKDSApi.kt new file mode 100644 index 0000000..f3898f4 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/annotation/ExperimentalKDSApi.kt @@ -0,0 +1,8 @@ +package `fun`.kotlingang.kds.annotation + + +@RequiresOptIn ( + message = "This API is unstable yet, probably you can see why in the kdoc", + level = RequiresOptIn.Level.WARNING +) +public annotation class ExperimentalKDSApi diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/DelegateProvider.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/DelegateProvider.kt new file mode 100644 index 0000000..ab0ad9b --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/DelegateProvider.kt @@ -0,0 +1,8 @@ +package `fun`.kotlingang.kds.delegate + +import kotlin.reflect.KProperty + + +public fun interface DelegateProvider { + public operator fun provideDelegate(thisRef: TReceiver, property: KProperty<*>): TDelegate +} diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageList.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageList.kt new file mode 100644 index 0000000..5f83fa3 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageList.kt @@ -0,0 +1,31 @@ +package `fun`.kotlingang.kds.delegate + +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi +import `fun`.kotlingang.kds.annotation.UnsafeKType +import `fun`.kotlingang.kds.mutate.StorageList +import `fun`.kotlingang.kds.storage.KTypeDataStorage +import kotlin.reflect.typeOf + + +/** + * @see [StorageList] + */ +@ExperimentalKDSApi +@OptIn(ExperimentalStdlibApi::class, UnsafeKType::class) +public inline fun KTypeDataStorage.storageList ( + list: MutableList +): DelegateProvider>> = + DelegateProvider { _, property -> + property { + StorageList(property.name, storage = this, list, typeOf>()) + } + } + +/** + * @see [StorageList] + */ +@ExperimentalKDSApi +public inline fun KTypeDataStorage.storageList ( + vararg elements: T +): DelegateProvider>> = storageList(mutableListOf(*elements)) + diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageMap.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageMap.kt new file mode 100644 index 0000000..0534b30 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageMap.kt @@ -0,0 +1,30 @@ +package `fun`.kotlingang.kds.delegate + +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi +import `fun`.kotlingang.kds.annotation.UnsafeKType +import `fun`.kotlingang.kds.mutate.StorageMap +import `fun`.kotlingang.kds.storage.KTypeDataStorage +import kotlin.reflect.typeOf + + +/** + * @see [StorageMap] + */ +@ExperimentalKDSApi +@OptIn(ExperimentalStdlibApi::class, UnsafeKType::class) +public inline fun KTypeDataStorage.storageMap ( + map: MutableMap +): DelegateProvider>> = + DelegateProvider { _, property -> + property { + StorageMap(property.name, storage = this, map, typeOf>()) + } + } + +/** + * @see [StorageMap] + */ +@ExperimentalKDSApi +public inline fun KTypeDataStorage.storageMap ( + vararg pairs: Pair +): DelegateProvider>> = storageMap(mutableMapOf(*pairs)) diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageSet.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageSet.kt new file mode 100644 index 0000000..239b8b0 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/delegate/storageSet.kt @@ -0,0 +1,31 @@ +package `fun`.kotlingang.kds.delegate + +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi +import `fun`.kotlingang.kds.annotation.UnsafeKType +import `fun`.kotlingang.kds.mutate.StorageSet +import `fun`.kotlingang.kds.storage.KTypeDataStorage +import kotlin.reflect.typeOf + + +/** + * @see [StorageSet] + */ +@ExperimentalKDSApi +@OptIn(ExperimentalStdlibApi::class, UnsafeKType::class) +public inline fun KTypeDataStorage.storageSet ( + map: MutableSet +): DelegateProvider>> = + DelegateProvider { _, property -> + property { + StorageSet(property.name, storage = this, map, typeOf>()) + } + } + +/** + * @see [StorageSet] + */ +@ExperimentalKDSApi +public inline fun KTypeDataStorage.storageSet ( + vararg elements: T +): DelegateProvider>> = storageSet(mutableSetOf(*elements)) + diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableList.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableList.kt new file mode 100644 index 0000000..ff9bb64 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableList.kt @@ -0,0 +1,64 @@ +package `fun`.kotlingang.kds.mutate + + +internal class SavableList ( + private val listSource: MutableList, + private val saveAction: SavableList.() -> Unit +) : MutableList by listSource { + private fun save() = saveAction() + + override fun add(element: T): Boolean = + listSource.add(element).apply { save() } + + override fun add(index: Int, element: T) { + listSource.add(index, element) + save() + } + + override fun addAll(elements: Collection): Boolean = + listSource.addAll(elements).apply { save() } + + override fun addAll(index: Int, elements: Collection): Boolean = + listSource.addAll(index, elements).apply { save() } + + override fun clear() { + listSource.clear() + save() + } + + private fun listIterator(iterator: MutableListIterator) = object : MutableListIterator by listIterator() { + override fun add(element: T) { + iterator.add(element) + save() + } + override fun remove() { + iterator.remove() + save() + } + override fun set(element: T) { + iterator.set(element) + save() + } + } + + override fun listIterator(): MutableListIterator = + listIterator(listSource.listIterator()) + + override fun listIterator(index: Int): MutableListIterator = + listIterator(listSource.listIterator(index)) + + override fun remove(element: T): Boolean = + listSource.remove(element).apply { save() } + + override fun removeAll(elements: Collection): Boolean = + listSource.retainAll(elements).apply { save() } + + override fun removeAt(index: Int): T = + listSource.removeAt(index).apply { save() } + + override fun retainAll(elements: Collection): Boolean = + listSource.retainAll(elements).apply { save() } + + override fun set(index: Int, element: T): T = + listSource.set(index, element).apply { save() } +} diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableMap.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableMap.kt new file mode 100644 index 0000000..c9ea13e --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableMap.kt @@ -0,0 +1,61 @@ +package `fun`.kotlingang.kds.mutate + + +internal class SavableMap ( + private val mapSource: MutableMap, + private val saveAction: SavableMap.() -> Unit +) : MutableMap by mapSource { + override val entries: MutableSet> + + init { + val set = SavableSet ( + setSource = mapSource.entries, + saveAction = { saveAction() } + ) + entries = object : MutableSet> by set { + override fun iterator(): MutableIterator> { + val iterator = set.iterator() + + return object : MutableIterator> by iterator { + override fun next(): MutableMap.MutableEntry { + val nextEntry = iterator.next() + return object : MutableMap.MutableEntry by nextEntry { + override fun setValue(newValue: V): V = nextEntry.setValue(newValue).apply { + saveAction() + } + } + } + } + } + } + } + + override val keys: MutableSet = + object : MutableSet by SavableSet ( + setSource = mapSource.keys, + saveAction = { saveAction() } + ) {} + + override val values: MutableList = + object : MutableList by SavableList ( + listSource = mapSource.values.toMutableList(), + saveAction = { saveAction() } + ) {} + + override fun clear() { + mapSource.clear() + saveAction() + } + + override fun put(key: K, value: V): V? = mapSource.put(key, value).apply { + saveAction() + } + + override fun putAll(from: Map): Unit = mapSource.putAll(from).let { + saveAction() + } + + override fun remove(key: K): V? = mapSource.remove(key).apply { + saveAction() + } +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableSet.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableSet.kt new file mode 100644 index 0000000..e217e0b --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/SavableSet.kt @@ -0,0 +1,43 @@ +package `fun`.kotlingang.kds.mutate + + +internal class SavableSet ( + private val setSource: MutableSet, + private val saveAction: SavableSet.() -> Unit +) : MutableSet by setSource { + override fun add(element: T): Boolean = setSource.add(element).apply { + saveAction() + } + + override fun addAll(elements: Collection): Boolean = setSource.addAll(elements).apply { + saveAction() + } + + override fun iterator(): MutableIterator { + val iterator = setSource.iterator() + + return object : MutableIterator by iterator { + override fun remove() { + iterator.remove() + saveAction() + } + } + } + + override fun clear() { + setSource.clear() + saveAction() + } + + override fun remove(element: T): Boolean = setSource.remove(element).apply { + saveAction() + } + + override fun removeAll(elements: Collection): Boolean = setSource.removeAll(elements).apply { + saveAction() + } + + override fun retainAll(elements: Collection): Boolean = setSource.retainAll(elements).apply { + saveAction() + } +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageList.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageList.kt new file mode 100644 index 0000000..d2f9c40 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageList.kt @@ -0,0 +1,25 @@ +package `fun`.kotlingang.kds.mutate + +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi +import `fun`.kotlingang.kds.annotation.RawSetterGetter +import `fun`.kotlingang.kds.annotation.UnsafeKType +import `fun`.kotlingang.kds.storage.KTypeDataStorage +import kotlin.reflect.KType + + +/** + * This API is experimental as [StorageMap] + * You can write your ideas to #16 + * After some testing time we probably will understand should we + * keep this types in library or not + */ +@ExperimentalKDSApi +@OptIn(UnsafeKType::class, RawSetterGetter::class) +public class StorageList @UnsafeKType constructor ( + private val storageKey: String, + private val storage: KTypeDataStorage, + private val listSource: MutableList, + private val listType: KType +) : MutableList by SavableList(listSource, saveAction = { + storage.putWithKType(storageKey, listType, listSource) +}) diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageMap.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageMap.kt new file mode 100644 index 0000000..50112aa --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageMap.kt @@ -0,0 +1,25 @@ +package `fun`.kotlingang.kds.mutate + +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi +import `fun`.kotlingang.kds.annotation.RawSetterGetter +import `fun`.kotlingang.kds.annotation.UnsafeKType +import `fun`.kotlingang.kds.storage.KTypeDataStorage +import kotlin.reflect.KType + + +/** + * This API is experimental since there are no ideas for clear implementation of it (exactly map) + * You can write your ideas to #16 + * After some testing time we probably will understand should we + * keep this types in library or not + */ +@ExperimentalKDSApi +@OptIn(UnsafeKType::class, RawSetterGetter::class) +public class StorageMap @UnsafeKType constructor ( + private val storageKey: String, + private val storage: KTypeDataStorage, + private val mapSource: MutableMap, + private val mapType: KType, +) : MutableMap by SavableMap(mapSource, saveAction = { + storage.putWithKType(storageKey, mapType, mapSource) +}) diff --git a/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageSet.kt b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageSet.kt new file mode 100644 index 0000000..b1cd859 --- /dev/null +++ b/core/src/commonMain/kotlin/fun/kotlingang/kds/mutate/StorageSet.kt @@ -0,0 +1,24 @@ +package `fun`.kotlingang.kds.mutate + +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi +import `fun`.kotlingang.kds.annotation.RawSetterGetter +import `fun`.kotlingang.kds.annotation.UnsafeKType +import `fun`.kotlingang.kds.storage.KTypeDataStorage +import kotlin.reflect.KType + +/** + * This API is experimental as [StorageMap] + * You can write your ideas to #16 + * After some testing time we probably will understand should we + * keep this types in library or not + */ +@ExperimentalKDSApi +@OptIn(UnsafeKType::class, RawSetterGetter::class) +public class StorageSet @UnsafeKType constructor ( + private val storageKey: String, + private val storage: KTypeDataStorage, + private val setSource: MutableSet, + private val setType: KType +) : MutableSet by SavableSet(setSource, saveAction = { + storage.putWithKType(storageKey, setType, setSource) +}) diff --git a/json/json-files/src/jvmTest/kotlin/StorageTests.kt b/json/json-files/src/jvmTest/kotlin/StorageTests.kt index 255ddc0..0da5476 100644 --- a/json/json-files/src/jvmTest/kotlin/StorageTests.kt +++ b/json/json-files/src/jvmTest/kotlin/StorageTests.kt @@ -1,5 +1,11 @@ +import Storage.launchesCount +import Storage.list +import Storage.random2 import `fun`.kotlingang.kds.KFileDataStorage +import `fun`.kotlingang.kds.annotation.ExperimentalKDSApi import `fun`.kotlingang.kds.delegate.property +import `fun`.kotlingang.kds.delegate.storageList +import `fun`.kotlingang.kds.delegate.storageMap import `fun`.kotlingang.kds.mutate.mutate import `fun`.kotlingang.kds.mutate.mutateBlocking import `fun`.kotlingang.kds.mutate.mutateCommit @@ -9,7 +15,11 @@ import org.junit.Test import kotlin.random.Random +@OptIn(ExperimentalKDSApi::class) object Storage : KFileDataStorage() { + val map by storageMap() + val list by storageList() + private val randomDelegate = property { Random.nextLong() } var random by randomDelegate @@ -17,7 +27,6 @@ object Storage : KFileDataStorage() { var launchesCount by property { 0 } - var list by property { mutableListOf() } val mutableList by property { mutableListOf() } } @@ -30,25 +39,24 @@ class StorageTests { Storage.setupBlocking() Storage.setup() println("Awaited loading") + println("Launches count: ${++launchesCount}") + println("Random value: $random2") - Storage.mutate { - println("Launches count: ${++launchesCount}") - println("Random value: $random2") + println("ListBefore: $list") - println("ListBefore: $list") + list += "Element" - mutate { - list += "Element" - } + println("List: $list") - println("List: $list") - } - Storage.commit() + Storage.mutateCommit { + mutableList += "AAA" + } } @Test - fun mutableStorageTest() = Storage.mutateBlocking { - mutableList += "Test" + fun storageListMapTest() = Storage.mutateBlocking { + list += "A" + map.compute("launches") { _, v -> (v ?: 0) + 1 } } }