diff --git a/CHANGELOG.md b/CHANGELOG.md index 106b6be27f0..ca0d593e9fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.11.10 + +* `Repos`: + * `Cache`: + * `KVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache` + * `SimpleKVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache` + * New `KVCache` subtype - `FullKVCache` + * Add `Full*` variants of standard repos + * Add `cached`/`caching` (for write repos) extensions for all standard types of repos + ## 0.11.9 * `Versions` diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt index 0c3a67521d0..4cb3e2eefd6 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/Optional.kt @@ -41,6 +41,13 @@ data class Optional internal constructor( inline val T.optional get() = Optional.presented(this) +inline val T?.optionalOrAbsentIfNull + get() = if (this == null) { + Optional.absent() + } else { + Optional.presented(this) + } + /** * Will call [block] when data presented ([Optional.dataPresented] == true) */ diff --git a/gradle.properties b/gradle.properties index a9641fc0848..368c2b7b56e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.11.9 -android_code_version=133 +version=0.11.10 +android_code_version=134 diff --git a/repos/cache/build.gradle b/repos/cache/build.gradle index e128b1e3cff..1ca610cbfdf 100644 --- a/repos/cache/build.gradle +++ b/repos/cache/build.gradle @@ -15,4 +15,4 @@ kotlin { } } } -} \ No newline at end of file +} diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CRUDCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CRUDCacheRepo.kt index 13289e997e3..c4facb233f5 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CRUDCacheRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CRUDCacheRepo.kt @@ -1,15 +1,15 @@ package dev.inmo.micro_utils.repos.cache import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.cache.KVCache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* open class ReadCRUDCacheRepo( - protected val parentRepo: ReadCRUDRepo, - protected val kvCache: KVCache, - protected val idGetter: (ObjectType) -> IdType + protected open val parentRepo: ReadCRUDRepo, + protected open val kvCache: KVCache, + protected open val idGetter: (ObjectType) -> IdType ) : ReadCRUDRepo by parentRepo { override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also { kvCache.set(id, it) @@ -18,8 +18,63 @@ open class ReadCRUDCacheRepo( override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) } +fun ReadCRUDRepo.cached( + kvCache: KVCache, + idGetter: (ObjectType) -> IdType +) = ReadCRUDCacheRepo(this, kvCache, idGetter) + +open class WriteCRUDCacheRepo( + protected open val parentRepo: WriteCRUDRepo, + protected open val kvCache: KVCache, + protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + protected open val idGetter: (ObjectType) -> IdType +) : WriteCRUDRepo { + override val newObjectsFlow: Flow by parentRepo::newObjectsFlow + override val updatedObjectsFlow: Flow by parentRepo::updatedObjectsFlow + override val deletedObjectsIdsFlow: Flow by parentRepo::deletedObjectsIdsFlow + + val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach { + kvCache.unset(it) + }.launchIn(scope) + + override suspend fun deleteById(ids: List) = parentRepo.deleteById(ids) + + override suspend fun update(values: List>): List { + val updated = parentRepo.update(values) + + kvCache.unset(values.map { it.id }) + kvCache.set(updated.associateBy { idGetter(it) }) + + return updated + } + + override suspend fun update(id: IdType, value: InputValueType): ObjectType? { + return parentRepo.update(id, value) ?.also { + kvCache.unset(id) + kvCache.set(idGetter(it), it) + } + } + + override suspend fun create(values: List): List { + val created = parentRepo.create(values) + + kvCache.set( + created.associateBy { idGetter(it) } + ) + + return created + } +} + +fun WriteCRUDRepo.caching( + kvCache: KVCache, + scope: CoroutineScope, + idGetter: (ObjectType) -> IdType +) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter) + + open class CRUDCacheRepo( - parentRepo: CRUDRepo, + override val parentRepo: CRUDRepo, kvCache: KVCache, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), idGetter: (ObjectType) -> IdType @@ -27,8 +82,17 @@ open class CRUDCacheRepo( parentRepo, kvCache, idGetter -), CRUDRepo, WriteCRUDRepo by parentRepo { - protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope) - protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope) - protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope) -} +), + WriteCRUDRepo by WriteCRUDCacheRepo( + parentRepo, + kvCache, + scope, + idGetter +), + CRUDRepo + +fun CRUDRepo.cached( + kvCache: KVCache, + scope: CoroutineScope, + idGetter: (ObjectType) -> IdType +) = CRUDCacheRepo(this, kvCache, scope, idGetter) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KVCache.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KVCache.kt index a7248352446..51f000ba299 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KVCache.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KVCache.kt @@ -1,41 +1,6 @@ package dev.inmo.micro_utils.repos.cache -import dev.inmo.micro_utils.repos.* -import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -interface KVCache : KeyValueRepo - -open class SimpleKVCache( - protected val cachedValuesCount: Int, - private val kvParent: KeyValueRepo = MapKeyValueRepo() -) : KVCache, KeyValueRepo by kvParent { - protected open val cacheStack = ArrayList(cachedValuesCount) - protected val syncMutex = Mutex() - - protected suspend fun makeUnset(toUnset: List) { - cacheStack.removeAll(toUnset) - kvParent.unset(toUnset) - } - - override suspend fun set(toSet: Map) { - syncMutex.withLock { - if (toSet.size > cachedValuesCount) { - cacheStack.clear() - - kvParent.unset(getAllWithNextPaging { kvParent.keys(it) }) - val keysToInclude = toSet.keys.drop(toSet.size - cachedValuesCount) - - cacheStack.addAll(keysToInclude) - kvParent.set(keysToInclude.associateWith { toSet.getValue(it) }) - } else { - makeUnset(cacheStack.take(toSet.size)) - } - } - } - - override suspend fun unset(toUnset: List) { - syncMutex.withLock { makeUnset(toUnset) } - } -} +@Deprecated("Replaced", ReplaceWith("KVCache", "dev.inmo.micro_utils.repos.cache.cache.KVCache")) +typealias KVCache = dev.inmo.micro_utils.repos.cache.cache.KVCache +@Deprecated("Replaced", ReplaceWith("SimpleKVCache", "dev.inmo.micro_utils.repos.cache.cache.SimpleKVCache")) +typealias SimpleKVCache = dev.inmo.micro_utils.repos.cache.cache.SimpleKVCache diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt index 79ae1392d1b..3e2265e4466 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt @@ -1,18 +1,25 @@ package dev.inmo.micro_utils.repos.cache +import dev.inmo.micro_utils.pagination.Pagination +import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.cache.KVCache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* open class ReadKeyValueCacheRepo( - protected val parentRepo: ReadKeyValueRepo, - protected val kvCache: KVCache, + protected open val parentRepo: ReadKeyValueRepo, + protected open val kvCache: KVCache, ) : ReadKeyValueRepo by parentRepo { override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) } +fun ReadKeyValueRepo.cached( + kvCache: KVCache +) = ReadKeyValueCacheRepo(this, kvCache) + open class KeyValueCacheRepo( parentRepo: KeyValueRepo, kvCache: KVCache, @@ -21,3 +28,8 @@ open class KeyValueCacheRepo( protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope) protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) } + +fun KeyValueRepo.cached( + kvCache: KVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) = KeyValueCacheRepo(this, kvCache, scope) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt index 1948279461c..afe2d202d8c 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt @@ -5,13 +5,14 @@ import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.utils.paginate import dev.inmo.micro_utils.pagination.utils.reverse import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.cache.KVCache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* open class ReadKeyValuesCacheRepo( - protected val parentRepo: ReadKeyValuesRepo, - protected val kvCache: KVCache> + protected open val parentRepo: ReadKeyValuesRepo, + protected open val kvCache: KVCache> ) : ReadKeyValuesRepo by parentRepo { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult { return kvCache.get(k) ?.paginate( @@ -29,6 +30,10 @@ open class ReadKeyValuesCacheRepo( override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) } +fun ReadKeyValuesRepo.cached( + kvCache: KVCache> +) = ReadKeyValuesCacheRepo(this, kvCache) + open class KeyValuesCacheRepo( parentRepo: KeyValuesRepo, kvCache: KVCache>, @@ -38,3 +43,8 @@ open class KeyValuesCacheRepo( protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope) protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope) } + +fun KeyValuesRepo.cached( + kvCache: KVCache>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) = KeyValuesCacheRepo(this, kvCache, scope) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt new file mode 100644 index 00000000000..961e687b260 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt @@ -0,0 +1,8 @@ +package dev.inmo.micro_utils.repos.cache.cache + +/** + * This interface declares that current type of [KVCache] will contains all the data all the time of its life + */ +interface FullKVCache : KVCache { + companion object +} diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt new file mode 100644 index 00000000000..4ab2c7563bc --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt @@ -0,0 +1,7 @@ +package dev.inmo.micro_utils.repos.cache.cache + +import dev.inmo.micro_utils.repos.* + +interface KVCache : KeyValueRepo { + companion object +} diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt new file mode 100644 index 00000000000..a5ee4639b3d --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt @@ -0,0 +1,28 @@ +package dev.inmo.micro_utils.repos.cache.cache + +import dev.inmo.micro_utils.repos.KeyValueRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +open class SimpleFullKVCache( + private val kvParent: KeyValueRepo = MapKeyValueRepo() +) : FullKVCache, KeyValueRepo by kvParent { + protected val syncMutex = Mutex() + + override suspend fun set(toSet: Map) { + syncMutex.withLock { + kvParent.set(toSet) + } + } + + override suspend fun unset(toUnset: List) { + syncMutex.withLock { + kvParent.unset(toUnset) + } + } +} + +inline fun FullKVCache( + kvParent: KeyValueRepo = MapKeyValueRepo() +) = SimpleFullKVCache(kvParent) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt new file mode 100644 index 00000000000..56fe1091383 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt @@ -0,0 +1,42 @@ +package dev.inmo.micro_utils.repos.cache.cache + +import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging +import dev.inmo.micro_utils.repos.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +open class SimpleKVCache( + protected val cachedValuesCount: Int, + private val kvParent: KeyValueRepo = MapKeyValueRepo() +) : KVCache, KeyValueRepo by kvParent { + protected open val cacheQueue = ArrayDeque(cachedValuesCount) + protected val syncMutex = Mutex() + + protected suspend fun makeUnset(toUnset: List) { + cacheQueue.removeAll(toUnset) + kvParent.unset(toUnset) + } + + override suspend fun set(toSet: Map) { + syncMutex.withLock { + for ((k, v) in toSet) { + if (cacheQueue.size >= cachedValuesCount) { + cacheQueue.removeFirstOrNull() ?.let { + kvParent.unset(it) + } + } + cacheQueue.addLast(k) + kvParent.set(k, v) + } + } + } + + override suspend fun unset(toUnset: List) { + syncMutex.withLock { makeUnset(toUnset) } + } +} + +inline fun KVCache( + cachedValuesCount: Int, + kvParent: KeyValueRepo = MapKeyValueRepo() +) = SimpleKVCache(cachedValuesCount, kvParent) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt new file mode 100644 index 00000000000..f5a76e06b1d --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt @@ -0,0 +1,95 @@ +package dev.inmo.micro_utils.repos.cache.full + +import dev.inmo.micro_utils.common.* +import dev.inmo.micro_utils.pagination.Pagination +import dev.inmo.micro_utils.pagination.PaginationResult +import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.* +import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import dev.inmo.micro_utils.repos.cache.cache.KVCache +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers + +open class FullReadCRUDCacheRepo( + protected open val parentRepo: ReadCRUDRepo, + protected open val kvCache: FullKVCache, + protected open val idGetter: (ObjectType) -> IdType +) : ReadCRUDRepo { + protected inline fun doOrTakeAndActualize( + action: FullKVCache.() -> Optional, + actionElse: ReadCRUDRepo.() -> T, + actualize: FullKVCache.(T) -> Unit + ): T { + kvCache.action().onPresented { + return it + }.onAbsent { + return parentRepo.actionElse().also { + kvCache.actualize(it) + } + } + error("The result should be returned above") + } + + protected suspend fun actualizeAll() { + kvCache.clear() + doForAllWithNextPaging { + parentRepo.getByPagination(it).also { + kvCache.set(it.results.associateBy { idGetter(it) }) + } + } + } + + override suspend fun getByPagination(pagination: Pagination): PaginationResult = doOrTakeAndActualize( + { values(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, + { getByPagination(pagination) }, + { if (it.results.isNotEmpty()) actualizeAll() } + ) + + override suspend fun count(): Long = doOrTakeAndActualize( + { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, + { count() }, + { if (it != 0L) actualizeAll() } + ) + + override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualize( + { contains(id).takeIf { it }.optionalOrAbsentIfNull }, + { contains(id) }, + { if (it) parentRepo.getById(id) ?.let { set(id, it) } } + ) + + override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize( + { get(id) ?.optional ?: Optional.absent() }, + { getById(id) }, + { it ?.let { set(idGetter(it), it) } } + ) +} + +fun ReadCRUDRepo.cached( + kvCache: FullKVCache, + idGetter: (ObjectType) -> IdType +) = FullReadCRUDCacheRepo(this, kvCache, idGetter) + +open class FullCRUDCacheRepo( + override val parentRepo: CRUDRepo, + kvCache: FullKVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + idGetter: (ObjectType) -> IdType +) : FullReadCRUDCacheRepo( + parentRepo, + kvCache, + idGetter +), + WriteCRUDRepo by WriteCRUDCacheRepo( + parentRepo, + kvCache, + scope, + idGetter + ), + CRUDRepo + +fun CRUDRepo.cached( + kvCache: FullKVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + idGetter: (ObjectType) -> IdType +) = FullCRUDCacheRepo(this, kvCache, scope, idGetter) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt new file mode 100644 index 00000000000..6e428846c25 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt @@ -0,0 +1,104 @@ +package dev.inmo.micro_utils.repos.cache.full + +import dev.inmo.micro_utils.common.* +import dev.inmo.micro_utils.pagination.Pagination +import dev.inmo.micro_utils.pagination.PaginationResult +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import dev.inmo.micro_utils.repos.pagination.getAll +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* + +open class FullReadKeyValueCacheRepo( + protected open val parentRepo: ReadKeyValueRepo, + protected open val kvCache: FullKVCache, +) : ReadKeyValueRepo { + protected inline fun doOrTakeAndActualize( + action: FullKVCache.() -> Optional, + actionElse: ReadKeyValueRepo.() -> T, + actualize: FullKVCache.(T) -> Unit + ): T { + kvCache.action().onPresented { + return it + }.onAbsent { + return parentRepo.actionElse().also { + kvCache.actualize(it) + } + } + error("The result should be returned above") + } + protected suspend fun actualizeAll() { + kvCache.clear() + kvCache.set(parentRepo.getAll { keys(it) }.toMap()) + } + + override suspend fun get(k: Key): Value? = doOrTakeAndActualize( + { get(k) ?.optional ?: Optional.absent() }, + { get(k) }, + { set(k, it ?: return@doOrTakeAndActualize) } + ) + + override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult = doOrTakeAndActualize( + { values(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, + { values(pagination, reversed) }, + { if (it.results.isNotEmpty()) actualizeAll() } + ) + + override suspend fun count(): Long = doOrTakeAndActualize( + { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, + { count() }, + { if (it != 0L) actualizeAll() } + ) + + override suspend fun contains(key: Key): Boolean = doOrTakeAndActualize( + { contains(key).takeIf { it }.optionalOrAbsentIfNull }, + { contains(key) }, + { if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } } + ) + + override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult = doOrTakeAndActualize( + { keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, + { keys(pagination, reversed) }, + { if (it.results.isNotEmpty()) actualizeAll() } + ) + + override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult = doOrTakeAndActualize( + { keys(v, pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, + { parentRepo.keys(v, pagination, reversed) }, + { if (it.results.isNotEmpty()) actualizeAll() } + ) +} + +fun ReadKeyValueRepo.cached( + kvCache: FullKVCache +) = FullReadKeyValueCacheRepo(this, kvCache) + +open class FullWriteKeyValueCacheRepo( + protected open val parentRepo: WriteKeyValueRepo, + protected open val kvCache: FullKVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : WriteKeyValueRepo by parentRepo { + protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope) + protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) +} + +fun WriteKeyValueRepo.caching( + kvCache: FullKVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) = FullWriteKeyValueCacheRepo(this, kvCache, scope) + +open class FullKeyValueCacheRepo( + parentRepo: KeyValueRepo, + kvCache: FullKVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : FullWriteKeyValueCacheRepo(parentRepo, kvCache, scope), + KeyValueRepo, + ReadKeyValueRepo by FullReadKeyValueCacheRepo(parentRepo, kvCache) { + override suspend fun unsetWithValues(toUnset: List) = parentRepo.unsetWithValues(toUnset) +} + +fun KeyValueRepo.cached( + kvCache: FullKVCache, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) = FullKeyValueCacheRepo(this, kvCache, scope) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt new file mode 100644 index 00000000000..014cde674e4 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt @@ -0,0 +1,154 @@ +package dev.inmo.micro_utils.repos.cache.full + +import dev.inmo.micro_utils.common.* +import dev.inmo.micro_utils.pagination.* +import dev.inmo.micro_utils.pagination.utils.* +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* + +open class FullReadKeyValuesCacheRepo( + protected open val parentRepo: ReadKeyValuesRepo, + protected open val kvCache: FullKVCache>, +) : ReadKeyValuesRepo { + protected inline fun doOrTakeAndActualize( + action: FullKVCache>.() -> Optional, + actionElse: ReadKeyValuesRepo.() -> T, + actualize: FullKVCache>.(T) -> Unit + ): T { + kvCache.action().onPresented { + return it + }.onAbsent { + return parentRepo.actionElse().also { + kvCache.actualize(it) + } + } + error("The result should be returned above") + } + + protected suspend fun actualizeKey(k: Key) { + kvCache.set(k, parentRepo.getAll(k)) + } + + protected suspend fun actualizeAll() { + doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } } + kvCache.set(parentRepo.getAll()) + } + + override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult { + return doOrTakeAndActualize( + { + get(k) ?.paginate( + pagination.let { if (reversed) it.reverse(count(k)) else it } + ) ?.let { + if (reversed) it.copy(results = it.results.reversed()) else it + }.optionalOrAbsentIfNull + }, + { get(k, pagination, reversed) }, + { actualizeKey(k) } + ) + } + + override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult { + return doOrTakeAndActualize( + { + kvCache.keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull + }, + { parentRepo.keys(pagination, reversed) }, + { actualizeAll() } + ) + } + + override suspend fun count(): Long = doOrTakeAndActualize( + { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, + { count() }, + { if (it != 0L) actualizeAll() } + ) + + override suspend fun count(k: Key): Long = doOrTakeAndActualize( + { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, + { count() }, + { if (it != 0L) actualizeKey(k) } + ) + + override suspend fun contains(k: Key, v: Value): Boolean = doOrTakeAndActualize( + { get(k) ?.contains(v).takeIf { it == true }.optionalOrAbsentIfNull }, + { contains(k, v) }, + { if (it) actualizeKey(k) } + ) + + override suspend fun contains(k: Key): Boolean = doOrTakeAndActualize( + { contains(k).takeIf { it }.optionalOrAbsentIfNull }, + { contains(k) }, + { if (it) actualizeKey(k) } + ) + + override suspend fun keys( + v: Value, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = doOrTakeAndActualize( + { + val keys = getAllWithNextPaging { keys(it) }.filter { get(it) ?.contains(v) == true }.optionallyReverse(reversed) + if (keys.isNotEmpty()) { + keys.paginate(pagination.optionallyReverse(keys.size, reversed)).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull + } else { + Optional.absent() + } + }, + { parentRepo.keys(v, pagination, reversed) }, + { if (it.results.isNotEmpty()) actualizeAll() } + ) + +} + +fun ReadKeyValuesRepo.cached( + kvCache: FullKVCache> +) = FullReadKeyValuesCacheRepo(this, kvCache) + +open class FullWriteKeyValuesCacheRepo( + protected open val parentRepo: WriteKeyValuesRepo, + protected open val kvCache: FullKVCache>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : WriteKeyValuesRepo by parentRepo { + protected val onNewJob = parentRepo.onNewValue.onEach { + kvCache.set( + it.first, + kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second) + ) + }.launchIn(scope) + protected val onRemoveJob = parentRepo.onValueRemoved.onEach { + kvCache.set( + it.first, + kvCache.get(it.first) ?.minus(it.second) ?: return@onEach + ) + }.launchIn(scope) +} + +fun WriteKeyValuesRepo.caching( + kvCache: FullKVCache>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) = FullWriteKeyValuesCacheRepo(this, kvCache, scope) + +open class FullKeyValuesCacheRepo( + parentRepo: KeyValuesRepo, + kvCache: FullKVCache>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : FullWriteKeyValuesCacheRepo(parentRepo, kvCache, scope), + KeyValuesRepo, + ReadKeyValuesRepo by FullReadKeyValuesCacheRepo(parentRepo, kvCache) { + override suspend fun clearWithValue(v: Value) { + doAllWithCurrentPaging { + keys(v, it).also { + remove(it.results.associateWith { listOf(v) }) + } + } + } +} + +fun KeyValuesRepo.caching( + kvCache: FullKVCache>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) = FullKeyValuesCacheRepo(this, kvCache, scope) diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt index 8ffe9094719..7a418269693 100644 --- a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt @@ -48,6 +48,10 @@ interface KeyValueRepo : ReadKeyValueRepo, WriteKeyValue } } } + + suspend fun clear() { + doAllWithCurrentPaging { keys(it).also { unset(it.results) } } + } } typealias StandardKeyValueRepo = KeyValueRepo