diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0288f92a5..bbac4a252ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.20.2 + +* All main repos uses `SmartRWLocker` +* `Versions`: + * `Serialization`: `1.5.1` -> `1.6.0` + * `Exposed`: `0.42.0` -> `0.42.1` + * `Korlibs`: `4.0.9` -> `4.0.10` +* `Androis SDK`: `33` -> `34` + ## 0.20.1 * `SmallTextField`: diff --git a/gradle.properties b/gradle.properties index 8bb22490175..24586dd8976 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.20.1 -android_code_version=207 +version=0.20.2 +android_code_version=208 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6afe972fe56..c036f67e734 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] kt = "1.9.0" -kt-serialization = "1.5.1" +kt-serialization = "1.6.0" kt-coroutines = "1.7.3" kslog = "1.2.0" jb-compose = "1.4.3" -jb-exposed = "0.42.0" +jb-exposed = "0.42.1" jb-dokka = "1.8.20" -korlibs = "4.0.9" +korlibs = "4.0.10" uuid = "0.8.0" ktor = "2.3.3" @@ -38,8 +38,8 @@ android-test = "1.1.5" android-compose-material3 = "1.1.1" android-props-minSdk = "21" -android-props-compileSdk = "33" -android-props-buildTools = "33.0.2" +android-props-compileSdk = "34" +android-props-buildTools = "34.0.0" [libraries] 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 4b58af9f4e2..c17dd5c70e9 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,5 +1,8 @@ package dev.inmo.micro_utils.repos.cache +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.util.actualizeAll @@ -10,32 +13,47 @@ import kotlinx.coroutines.flow.* open class ReadCRUDCacheRepo( protected open val parentRepo: ReadCRUDRepo, protected open val kvCache: KVCache, + protected val locker: SmartRWLocker = SmartRWLocker(), protected open val idGetter: (ObjectType) -> IdType ) : ReadCRUDRepo by parentRepo, CommonCacheRepo { - override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also { - kvCache.set(id, it) + override suspend fun getById(id: IdType): ObjectType? = locker.withReadAcquire { + kvCache.get(id) + } ?: (parentRepo.getById(id) ?.also { + locker.withWriteLock { + kvCache.set(id, it) + } }) override suspend fun getAll(): Map { - return kvCache.getAll().takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { - kvCache.actualizeAll(true) { it } + return locker.withReadAcquire { + kvCache.getAll() + }.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { + locker.withWriteLock { + kvCache.actualizeAll(true) { it } + } } } - override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) + override suspend fun contains(id: IdType): Boolean = locker.withReadAcquire { + kvCache.contains(id) + } || parentRepo.contains(id) - override suspend fun invalidate() = kvCache.clear() + override suspend fun invalidate() = locker.withWriteLock { + kvCache.clear() + } } fun ReadCRUDRepo.cached( kvCache: KVCache, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType -) = ReadCRUDCacheRepo(this, kvCache, idGetter) +) = ReadCRUDCacheRepo(this, kvCache, locker, idGetter) open class WriteCRUDCacheRepo( protected open val parentRepo: WriteCRUDRepo, - protected open val kvCache: KVCache, + protected open val kvCache: KeyValueRepo, protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + protected val locker: SmartRWLocker = SmartRWLocker(), protected open val idGetter: (ObjectType) -> IdType ) : WriteCRUDRepo, CommonCacheRepo { override val newObjectsFlow: Flow by parentRepo::newObjectsFlow @@ -43,15 +61,15 @@ open class WriteCRUDCacheRepo( override val deletedObjectsIdsFlow: Flow by parentRepo::deletedObjectsIdsFlow val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach { - kvCache.set(idGetter(it), it) + locker.withWriteLock { kvCache.set(idGetter(it), it) } }.launchIn(scope) val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach { - kvCache.set(idGetter(it), it) + locker.withWriteLock { kvCache.set(idGetter(it), it) } }.launchIn(scope) val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach { - kvCache.unset(it) + locker.withWriteLock { kvCache.unset(it) } }.launchIn(scope) override suspend fun deleteById(ids: List) = parentRepo.deleteById(ids) @@ -59,53 +77,65 @@ open class WriteCRUDCacheRepo( override suspend fun update(values: List>): List { val updated = parentRepo.update(values) - kvCache.unset(values.map { it.id }) - kvCache.set(updated.associateBy { idGetter(it) }) + locker.withWriteLock { + 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) + locker.withWriteLock { + 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) } - ) + locker.withWriteLock { + kvCache.set( + created.associateBy { idGetter(it) } + ) + } return created } - override suspend fun invalidate() = kvCache.clear() + override suspend fun invalidate() = locker.withWriteLock { + kvCache.clear() + } } fun WriteCRUDRepo.caching( kvCache: KVCache, scope: CoroutineScope, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType -) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter) +) = WriteCRUDCacheRepo(this, kvCache, scope, locker, idGetter) open class CRUDCacheRepo( override val parentRepo: CRUDRepo, kvCache: KVCache, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType ) : ReadCRUDCacheRepo( parentRepo, kvCache, + locker, idGetter ), WriteCRUDRepo by WriteCRUDCacheRepo( parentRepo, kvCache, scope, + locker, idGetter ), CRUDRepo @@ -113,5 +143,6 @@ open class CRUDCacheRepo( fun CRUDRepo.cached( kvCache: KVCache, scope: CoroutineScope, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType -) = CRUDCacheRepo(this, kvCache, scope, idGetter) +) = CRUDCacheRepo(this, kvCache, scope, locker, idGetter) 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 aba9a20c00e..6125fcf0b5e 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,5 +1,8 @@ package dev.inmo.micro_utils.repos.cache +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.cache.cache.KVCache @@ -10,50 +13,82 @@ import kotlinx.coroutines.flow.* open class ReadKeyValueCacheRepo( protected open val parentRepo: ReadKeyValueRepo, protected open val kvCache: KVCache, + protected val locker: SmartRWLocker = SmartRWLocker(), ) : ReadKeyValueRepo by parentRepo, CommonCacheRepo { - 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) + override suspend fun get(k: Key): Value? = locker.withReadAcquire { + kvCache.get(k) + } ?: parentRepo.get(k) ?.also { + locker.withWriteLock { + kvCache.set(k, it) + } + } + override suspend fun contains(key: Key): Boolean = locker.withReadAcquire { + kvCache.contains(key) + } || parentRepo.contains(key) override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult { - return keys(pagination, reversed).let { - it.changeResultsUnchecked( - it.results.mapNotNull { - get(it) - } - ) + return locker.withReadAcquire { + keys(pagination, reversed).let { + it.changeResultsUnchecked( + it.results.mapNotNull { + get(it) + } + ) + } } } - override suspend fun getAll(): Map = kvCache.getAll().takeIf { + override suspend fun getAll(): Map = locker.withReadAcquire { + kvCache.getAll() + }.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { - kvCache.set(it) + locker.withWriteLock { + kvCache.set(it) + } } - override suspend fun invalidate() = kvCache.clear() + override suspend fun invalidate() = locker.withWriteLock { + kvCache.clear() + } } fun ReadKeyValueRepo.cached( - kvCache: KVCache -) = ReadKeyValueCacheRepo(this, kvCache) + kvCache: KVCache, + locker: SmartRWLocker = SmartRWLocker(), +) = ReadKeyValueCacheRepo(this, kvCache, locker) open class KeyValueCacheRepo( override val parentRepo: KeyValueRepo, kvCache: KVCache, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) : ReadKeyValueCacheRepo(parentRepo, kvCache), KeyValueRepo, WriteKeyValueRepo by parentRepo, CommonCacheRepo { - 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) + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + locker: SmartRWLocker = SmartRWLocker(), +) : ReadKeyValueCacheRepo(parentRepo, kvCache, locker), KeyValueRepo, WriteKeyValueRepo by parentRepo, CommonCacheRepo { + protected val onNewJob = parentRepo.onNewValue.onEach { + locker.withWriteLock { + kvCache.set(it.first, it.second) + } + }.launchIn(scope) + protected val onRemoveJob = parentRepo.onValueRemoved.onEach { + locker.withWriteLock { + kvCache.unset(it) + } + }.launchIn(scope) - override suspend fun invalidate() = kvCache.clear() + override suspend fun invalidate() = locker.withWriteLock { + kvCache.clear() + } override suspend fun clear() { parentRepo.clear() - kvCache.clear() + locker.withWriteLock { + kvCache.clear() + } } } fun KeyValueRepo.cached( kvCache: KVCache, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = KeyValueCacheRepo(this, kvCache, scope) + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + locker: SmartRWLocker = SmartRWLocker(), +) = KeyValueCacheRepo(this, kvCache, scope, locker) 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 d4db6d676ed..bf8857b60ab 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 @@ -1,5 +1,8 @@ package dev.inmo.micro_utils.repos.cache +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.repos.* @@ -10,59 +13,85 @@ import kotlinx.coroutines.flow.* open class ReadKeyValuesCacheRepo( protected open val parentRepo: ReadKeyValuesRepo, - protected open val kvCache: KVCache> + protected open val kvCache: KVCache>, + protected val locker: SmartRWLocker = SmartRWLocker(), ) : ReadKeyValuesRepo by parentRepo, CommonCacheRepo { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult { - return getAll(k, reversed).paginate( + return locker.withReadAcquire { + getAll(k, reversed) + }.paginate( pagination ) } override suspend fun getAll(k: Key, reversed: Boolean): List { - return kvCache.get(k) ?.let { + return locker.withReadAcquire { + kvCache.get(k) + } ?.let { if (reversed) it.reversed() else it } ?: parentRepo.getAll(k, reversed).also { - kvCache.set(k, it) + locker.withWriteLock { + kvCache.set(k, it) + } } } - override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: (parentRepo.contains(k, v).also { + override suspend fun contains(k: Key, v: Value): Boolean = locker.withReadAcquire { + kvCache.get(k) + } ?.contains(v) ?: (parentRepo.contains(k, v).also { if (it) { - kvCache.unset(k) // clear as invalid + locker.withWriteLock { + kvCache.unset(k) // clear as invalid + } } }) - override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) + override suspend fun contains(k: Key): Boolean = locker.withReadAcquire { + kvCache.contains(k) + } || parentRepo.contains(k) - override suspend fun invalidate() = kvCache.clear() + override suspend fun invalidate() = locker.withWriteLock { + kvCache.clear() + } } fun ReadKeyValuesRepo.cached( - kvCache: KVCache> -) = ReadKeyValuesCacheRepo(this, kvCache) + kvCache: KVCache>, + locker: SmartRWLocker = SmartRWLocker(), +) = ReadKeyValuesCacheRepo(this, kvCache, locker) open class KeyValuesCacheRepo( parentRepo: KeyValuesRepo, kvCache: KVCache>, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) : ReadKeyValuesCacheRepo(parentRepo, kvCache), KeyValuesRepo, WriteKeyValuesRepo by parentRepo, CommonCacheRepo { + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + locker: SmartRWLocker = SmartRWLocker(), +) : ReadKeyValuesCacheRepo(parentRepo, kvCache, locker), KeyValuesRepo, WriteKeyValuesRepo by parentRepo, CommonCacheRepo { protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) -> - kvCache.set( - k, - kvCache.get(k) ?.plus(v) ?: return@onEach - ) + locker.withWriteLock { + kvCache.set( + k, + kvCache.get(k) ?.plus(v) ?: return@onEach + ) + } }.launchIn(scope) protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) -> - kvCache.set( - k, - kvCache.get(k) ?.minus(v) ?: return@onEach - ) + locker.withWriteLock { + kvCache.set( + k, + kvCache.get(k)?.minus(v) ?: return@onEach + ) + } }.launchIn(scope) protected val onDataClearedJob = parentRepo.onDataCleared.onEach { - kvCache.unset(it) + locker.withWriteLock { + kvCache.unset(it) + } }.launchIn(scope) - override suspend fun invalidate() = kvCache.clear() + override suspend fun invalidate() = locker.withWriteLock { + kvCache.clear() + } } fun KeyValuesRepo.cached( kvCache: KVCache>, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = KeyValuesCacheRepo(this, kvCache, scope) + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + locker: SmartRWLocker = SmartRWLocker(), +) = KeyValuesCacheRepo(this, kvCache, scope, locker) 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 index 961e687b260..7d0eb98328c 100644 --- 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 @@ -3,6 +3,7 @@ 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 */ +@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("KeyValueRepo", "dev.inmo.micro_utils.repos.KeyValueRepo")) interface FullKVCache : KVCache { 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 index 2514b84068b..1e760576244 100644 --- 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 @@ -5,7 +5,9 @@ import dev.inmo.micro_utils.repos.MapKeyValueRepo import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -open class SimpleFullKVCache( +@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("MapKeyValueRepo()", "dev.inmo.micro_utils.repos.MapKeyValueRepo")) + +class SimpleFullKVCache( private val kvParent: KeyValueRepo = MapKeyValueRepo() ) : FullKVCache, KeyValueRepo by kvParent { protected val syncMutex = Mutex() @@ -29,6 +31,7 @@ open class SimpleFullKVCache( } } +@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("kvParent", "dev.inmo.micro_utils.repos.MapKeyValueRepo")) inline fun FullKVCache( kvParent: KeyValueRepo = MapKeyValueRepo() ) = SimpleFullKVCache(kvParent) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheCRUDRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheCRUDRepo.kt index cc28b5245d7..1fbf7c29641 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheCRUDRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheCRUDRepo.kt @@ -1,8 +1,9 @@ package dev.inmo.micro_utils.repos.cache.fallback.crud import dev.inmo.micro_utils.repos.CRUDRepo +import dev.inmo.micro_utils.repos.KeyValueRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo import dev.inmo.micro_utils.repos.WriteCRUDRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import kotlinx.coroutines.CoroutineScope import kotlin.time.Duration.Companion.seconds @@ -10,7 +11,7 @@ import kotlin.time.Duration.Companion.seconds open class AutoRecacheCRUDRepo( originalRepo: CRUDRepo, scope: CoroutineScope, - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, actionWrapper: ActionWrapper = ActionWrapper.Direct, idGetter: (RegisteredObject) -> Id @@ -29,7 +30,7 @@ open class AutoRecacheCRUDRepo( originalRepo: CRUDRepo, scope: CoroutineScope, originalCallTimeoutMillis: Long, - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, idGetter: (RegisteredObject) -> Id ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheReadCRUDRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheReadCRUDRepo.kt index 7b1b915e240..2a08cbe63c5 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheReadCRUDRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheReadCRUDRepo.kt @@ -3,8 +3,9 @@ package dev.inmo.micro_utils.repos.cache.fallback.crud import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.PaginationResult +import dev.inmo.micro_utils.repos.KeyValueRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo import dev.inmo.micro_utils.repos.ReadCRUDRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo @@ -18,7 +19,7 @@ import kotlin.time.Duration.Companion.seconds open class AutoRecacheReadCRUDRepo( protected open val originalRepo: ReadCRUDRepo, protected val scope: CoroutineScope, - protected val kvCache: FullKVCache = FullKVCache(), + protected val kvCache: KeyValueRepo = MapKeyValueRepo(), protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds, protected val actionWrapper: ActionWrapper = ActionWrapper.Direct, protected val idGetter: (RegisteredObject) -> Id @@ -35,7 +36,7 @@ open class AutoRecacheReadCRUDRepo( originalRepo: ReadCRUDRepo, scope: CoroutineScope, originalCallTimeoutMillis: Long, - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, idGetter: (RegisteredObject) -> Id ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheWriteCRUDRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheWriteCRUDRepo.kt index f3ff4c15ff2..762b98b5f12 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheWriteCRUDRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheWriteCRUDRepo.kt @@ -1,23 +1,17 @@ package dev.inmo.micro_utils.repos.cache.fallback.crud -import dev.inmo.micro_utils.coroutines.plus import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.micro_utils.repos.UpdatedValuePair -import dev.inmo.micro_utils.repos.WriteCRUDRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo -import dev.inmo.micro_utils.repos.set -import dev.inmo.micro_utils.repos.unset import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge open class AutoRecacheWriteCRUDRepo( protected val originalRepo: WriteCRUDRepo, protected val scope: CoroutineScope, - protected val kvCache: FullKVCache = FullKVCache(), + protected val kvCache: KeyValueRepo = MapKeyValueRepo(), protected val idGetter: (RegisteredObject) -> Id ) : WriteCRUDRepo, FallbackCacheRepo { override val deletedObjectsIdsFlow: Flow diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheKeyValueRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheKeyValueRepo.kt index 42f5d05e574..be3ca90e7c4 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheKeyValueRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheKeyValueRepo.kt @@ -1,8 +1,8 @@ package dev.inmo.micro_utils.repos.cache.fallback.keyvalue import dev.inmo.micro_utils.repos.KeyValueRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo import dev.inmo.micro_utils.repos.WriteKeyValueRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import kotlinx.coroutines.CoroutineScope import kotlin.time.Duration.Companion.seconds @@ -10,7 +10,7 @@ import kotlin.time.Duration.Companion.seconds open class AutoRecacheKeyValueRepo( override val originalRepo: KeyValueRepo, scope: CoroutineScope, - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, actionWrapper: ActionWrapper = ActionWrapper.Direct, idGetter: (RegisteredObject) -> Id @@ -29,7 +29,7 @@ open class AutoRecacheKeyValueRepo( originalRepo: KeyValueRepo, scope: CoroutineScope, originalCallTimeoutMillis: Long, - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, idGetter: (RegisteredObject) -> Id ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheReadKeyValueRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheReadKeyValueRepo.kt index c3fa08ddd27..e8826b6fc08 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheReadKeyValueRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheReadKeyValueRepo.kt @@ -3,8 +3,9 @@ package dev.inmo.micro_utils.repos.cache.fallback.keyvalue import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.PaginationResult +import dev.inmo.micro_utils.repos.KeyValueRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo import dev.inmo.micro_utils.repos.ReadKeyValueRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo @@ -18,7 +19,7 @@ import kotlin.time.Duration.Companion.seconds open class AutoRecacheReadKeyValueRepo( protected open val originalRepo: ReadKeyValueRepo, protected val scope: CoroutineScope, - protected val kvCache: FullKVCache = FullKVCache(), + protected val kvCache: KeyValueRepo = MapKeyValueRepo(), protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds, protected val actionWrapper: ActionWrapper = ActionWrapper.Direct, protected val idGetter: (RegisteredObject) -> Id @@ -35,7 +36,7 @@ open class AutoRecacheReadKeyValueRepo( originalRepo: ReadKeyValueRepo, scope: CoroutineScope, originalCallTimeoutMillis: Long, - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, idGetter: (RegisteredObject) -> Id ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheWriteKeyValueRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheWriteKeyValueRepo.kt index 68f18b5391e..0bea2516d57 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheWriteKeyValueRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheWriteKeyValueRepo.kt @@ -1,12 +1,8 @@ package dev.inmo.micro_utils.repos.cache.fallback.keyvalue -import dev.inmo.micro_utils.coroutines.plus import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.micro_utils.repos.WriteKeyValueRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo -import dev.inmo.micro_utils.repos.set -import dev.inmo.micro_utils.repos.unset import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged @@ -14,7 +10,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged open class AutoRecacheWriteKeyValueRepo( protected val originalRepo: WriteKeyValueRepo, protected val scope: CoroutineScope, - protected val kvCache: FullKVCache = FullKVCache() + protected val kvCache: KeyValueRepo = MapKeyValueRepo() ) : WriteKeyValueRepo, FallbackCacheRepo { override val onValueRemoved: Flow get() = (originalRepo.onValueRemoved).distinctUntilChanged() diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheKeyValueRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheKeyValueRepo.kt index c51f852932c..c6ece8797ea 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheKeyValueRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheKeyValueRepo.kt @@ -1,8 +1,9 @@ package dev.inmo.micro_utils.repos.cache.fallback.keyvalues +import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValuesRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo import dev.inmo.micro_utils.repos.WriteKeyValuesRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import kotlinx.coroutines.CoroutineScope import kotlin.time.Duration.Companion.seconds @@ -10,7 +11,7 @@ import kotlin.time.Duration.Companion.seconds open class AutoRecacheKeyValuesRepo( override val originalRepo: KeyValuesRepo, scope: CoroutineScope, - kvCache: FullKVCache> = FullKVCache(), + kvCache: KeyValueRepo> = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds, actionWrapper: ActionWrapper = ActionWrapper.Direct ) : AutoRecacheReadKeyValuesRepo ( @@ -27,7 +28,7 @@ open class AutoRecacheKeyValuesRepo( originalRepo: KeyValuesRepo, scope: CoroutineScope, originalCallTimeoutMillis: Long, - kvCache: FullKVCache> = FullKVCache(), + kvCache: KeyValueRepo> = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis)) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheReadKeyValuesRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheReadKeyValuesRepo.kt index 20b521771b2..1bbdf83711e 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheReadKeyValuesRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheReadKeyValuesRepo.kt @@ -10,8 +10,9 @@ import dev.inmo.micro_utils.pagination.firstIndex import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.optionallyReverse import dev.inmo.micro_utils.pagination.utils.paginate +import dev.inmo.micro_utils.repos.KeyValueRepo +import dev.inmo.micro_utils.repos.MapKeyValueRepo import dev.inmo.micro_utils.repos.ReadKeyValuesRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo @@ -25,7 +26,7 @@ import kotlin.time.Duration.Companion.seconds open class AutoRecacheReadKeyValuesRepo( protected open val originalRepo: ReadKeyValuesRepo, protected val scope: CoroutineScope, - protected val kvCache: FullKVCache> = FullKVCache(), + protected val kvCache: KeyValueRepo> = MapKeyValueRepo(), protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds, protected val actionWrapper: ActionWrapper = ActionWrapper.Direct ) : ReadKeyValuesRepo, FallbackCacheRepo { @@ -41,7 +42,7 @@ open class AutoRecacheReadKeyValuesRepo( originalRepo: ReadKeyValuesRepo, scope: CoroutineScope, originalCallTimeoutMillis: Long, - kvCache: FullKVCache> = FullKVCache(), + kvCache: KeyValueRepo> = MapKeyValueRepo(), recacheDelay: Long = 60.seconds.inWholeMilliseconds ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis)) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheWriteKeyValuesRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheWriteKeyValuesRepo.kt index cd34593ec3c..14a4902c729 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheWriteKeyValuesRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheWriteKeyValuesRepo.kt @@ -1,15 +1,10 @@ package dev.inmo.micro_utils.repos.cache.fallback.keyvalues -import dev.inmo.micro_utils.coroutines.plus import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.micro_utils.pagination.FirstPagePagination import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging -import dev.inmo.micro_utils.repos.WriteKeyValuesRepo -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo import dev.inmo.micro_utils.repos.pagination.maxPagePagination -import dev.inmo.micro_utils.repos.set -import dev.inmo.micro_utils.repos.unset import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged @@ -17,7 +12,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged open class AutoRecacheWriteKeyValuesRepo( protected val originalRepo: WriteKeyValuesRepo, protected val scope: CoroutineScope, - protected val kvCache: FullKVCache> = FullKVCache() + protected val kvCache: KeyValueRepo> = MapKeyValueRepo() ) : WriteKeyValuesRepo, FallbackCacheRepo { override val onValueRemoved: Flow> get() = originalRepo.onValueRemoved 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 index d1868173dbc..22b34ffec54 100644 --- 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 @@ -1,38 +1,39 @@ package dev.inmo.micro_utils.repos.cache.full import dev.inmo.micro_utils.common.* +import dev.inmo.micro_utils.coroutines.SmartRWLocker import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock 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.* -import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.util.actualizeAll import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers open class FullReadCRUDCacheRepo( protected open val parentRepo: ReadCRUDRepo, - protected open val kvCache: FullKVCache, + protected open val kvCache: KeyValueRepo, + protected val locker: SmartRWLocker = SmartRWLocker(), protected open val idGetter: (ObjectType) -> IdType ) : ReadCRUDRepo, FullCacheRepo { - protected inline fun doOrTakeAndActualize( - action: FullKVCache.() -> Optional, + protected suspend inline fun doOrTakeAndActualize( + action: KeyValueRepo.() -> Optional, actionElse: ReadCRUDRepo.() -> T, - actualize: FullKVCache.(T) -> Unit + actualize: KeyValueRepo.(T) -> Unit ): T { - kvCache.action().onPresented { - return it - }.onAbsent { - return parentRepo.actionElse().also { - kvCache.actualize(it) - } + locker.withReadAcquire { + kvCache.action().onPresented { return it } + } + return parentRepo.actionElse().also { + locker.withWriteLock { kvCache.actualize(it) } } - error("The result should be returned above") } protected open suspend fun actualizeAll() { - kvCache.actualizeAll(parentRepo) + locker.withWriteLock { kvCache.actualizeAll(parentRepo) } } override suspend fun getByPagination(pagination: Pagination): PaginationResult = doOrTakeAndActualize( @@ -77,25 +78,29 @@ open class FullReadCRUDCacheRepo( } fun ReadCRUDRepo.cached( - kvCache: FullKVCache, + kvCache: KeyValueRepo, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType -) = FullReadCRUDCacheRepo(this, kvCache, idGetter) +) = FullReadCRUDCacheRepo(this, kvCache, locker, idGetter) open class FullCRUDCacheRepo( override val parentRepo: CRUDRepo, - kvCache: FullKVCache, + kvCache: KeyValueRepo, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType ) : FullReadCRUDCacheRepo( parentRepo, kvCache, + locker, idGetter ), WriteCRUDRepo by WriteCRUDCacheRepo( parentRepo, kvCache, scope, + locker, idGetter ), CRUDRepo { @@ -111,16 +116,18 @@ open class FullCRUDCacheRepo( } fun CRUDRepo.fullyCached( - kvCache: FullKVCache = FullKVCache(), + kvCache: KeyValueRepo = MapKeyValueRepo(), scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType -) = FullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, idGetter) +) = FullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, locker, idGetter) @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope, idGetter)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) fun CRUDRepo.cached( - kvCache: FullKVCache, + kvCache: KeyValueRepo, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker(), idGetter: (ObjectType) -> IdType -) = fullyCached(kvCache, scope, skipStartInvalidate, idGetter) +) = fullyCached(kvCache, scope, skipStartInvalidate, locker, 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 index e75d9ef95be..898bab97e09 100644 --- 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 @@ -1,11 +1,13 @@ package dev.inmo.micro_utils.repos.cache.full import dev.inmo.micro_utils.common.* +import dev.inmo.micro_utils.coroutines.SmartRWLocker import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock 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.cache.util.actualizeAll import dev.inmo.micro_utils.repos.pagination.getAll import kotlinx.coroutines.CoroutineScope @@ -14,25 +16,26 @@ import kotlinx.coroutines.flow.* open class FullReadKeyValueCacheRepo( protected open val parentRepo: ReadKeyValueRepo, - protected open val kvCache: FullKVCache, + protected open val kvCache: KeyValueRepo, + protected val locker: SmartRWLocker = SmartRWLocker() ) : ReadKeyValueRepo, FullCacheRepo { - protected inline fun doOrTakeAndActualize( - action: FullKVCache.() -> Optional, + protected suspend inline fun doOrTakeAndActualize( + action: KeyValueRepo.() -> Optional, actionElse: ReadKeyValueRepo.() -> T, - actualize: FullKVCache.(T) -> Unit + actualize: KeyValueRepo.(T) -> Unit ): T { - kvCache.action().onPresented { - return it - }.onAbsent { - return parentRepo.actionElse().also { - kvCache.actualize(it) - } + locker.withReadAcquire { + kvCache.action().onPresented { return it } + } + return parentRepo.actionElse().also { + locker.withWriteLock { kvCache.actualize(it) } } - error("The result should be returned above") } protected open suspend fun actualizeAll() { - kvCache.clear() - kvCache.set(parentRepo.getAll { keys(it) }.toMap()) + locker.withWriteLock { + kvCache.clear() + kvCache.set(parentRepo.getAll { keys(it) }.toMap()) + } } override suspend fun get(k: Key): Value? = doOrTakeAndActualize( @@ -83,37 +86,51 @@ open class FullReadKeyValueCacheRepo( } fun ReadKeyValueRepo.cached( - kvCache: FullKVCache -) = FullReadKeyValueCacheRepo(this, kvCache) + kvCache: KeyValueRepo, + locker: SmartRWLocker = SmartRWLocker() +) = FullReadKeyValueCacheRepo(this, kvCache, locker) open class FullWriteKeyValueCacheRepo( parentRepo: WriteKeyValueRepo, - protected open val kvCache: FullKVCache, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) + protected open val kvCache: KeyValueRepo, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + protected val locker: SmartRWLocker = SmartRWLocker() ) : WriteKeyValueRepo by parentRepo, FullCacheRepo { - 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) + protected val onNewJob = parentRepo.onNewValue.onEach { + locker.withWriteLock { + kvCache.set(it.first, it.second) + } + }.launchIn(scope) + protected val onRemoveJob = parentRepo.onValueRemoved.onEach { + locker.withWriteLock { + kvCache.unset(it) + } + }.launchIn(scope) override suspend fun invalidate() { - kvCache.clear() + locker.withWriteLock { + kvCache.clear() + } } } fun WriteKeyValueRepo.caching( - kvCache: FullKVCache, + kvCache: KeyValueRepo, scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) = FullWriteKeyValueCacheRepo(this, kvCache, scope) open class FullKeyValueCacheRepo( protected open val parentRepo: KeyValueRepo, - kvCache: FullKVCache, + kvCache: KeyValueRepo, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), - skipStartInvalidate: Boolean = false + skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker() ) : FullWriteKeyValueCacheRepo(parentRepo, kvCache, scope), KeyValueRepo, ReadKeyValueRepo by FullReadKeyValueCacheRepo( parentRepo, - kvCache + kvCache, + locker ) { init { if (!skipStartInvalidate) { @@ -124,7 +141,9 @@ open class FullKeyValueCacheRepo( override suspend fun unsetWithValues(toUnset: List) = parentRepo.unsetWithValues(toUnset) override suspend fun invalidate() { - kvCache.actualizeAll(parentRepo) + locker.withWriteLock { + kvCache.actualizeAll(parentRepo) + } } override suspend fun clear() { @@ -134,12 +153,16 @@ open class FullKeyValueCacheRepo( } fun KeyValueRepo.fullyCached( - kvCache: FullKVCache = FullKVCache(), - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = FullKeyValueCacheRepo(this, kvCache, scope) + kvCache: KeyValueRepo = MapKeyValueRepo(), + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker() +) = FullKeyValueCacheRepo(this, kvCache, scope, skipStartInvalidate, locker) @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) fun KeyValueRepo.cached( - kvCache: FullKVCache, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = fullyCached(kvCache, scope) + kvCache: KeyValueRepo, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker() +) = fullyCached(kvCache, scope, skipStartInvalidate, locker) 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 index 8d7ea99e5d1..5e1ff38e235 100644 --- 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 @@ -1,11 +1,13 @@ package dev.inmo.micro_utils.repos.cache.full import dev.inmo.micro_utils.common.* +import dev.inmo.micro_utils.coroutines.SmartRWLocker import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock 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 dev.inmo.micro_utils.repos.cache.util.actualizeAll import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -13,29 +15,32 @@ import kotlinx.coroutines.flow.* open class FullReadKeyValuesCacheRepo( protected open val parentRepo: ReadKeyValuesRepo, - protected open val kvCache: FullKVCache>, + protected open val kvCache: KeyValueRepo>, + protected val locker: SmartRWLocker = SmartRWLocker(), ) : ReadKeyValuesRepo, FullCacheRepo { - protected inline fun doOrTakeAndActualize( - action: FullKVCache>.() -> Optional, + protected suspend inline fun doOrTakeAndActualize( + action: KeyValueRepo>.() -> Optional, actionElse: ReadKeyValuesRepo.() -> T, - actualize: FullKVCache>.(T) -> Unit + actualize: KeyValueRepo>.(T) -> Unit ): T { - kvCache.action().onPresented { - return it - }.onAbsent { - return parentRepo.actionElse().also { - kvCache.actualize(it) - } + locker.withReadAcquire { + kvCache.action().onPresented { return it } + } + return parentRepo.actionElse().also { + locker.withWriteLock { kvCache.actualize(it) } } - error("The result should be returned above") } protected open suspend fun actualizeKey(k: Key) { - kvCache.set(k, parentRepo.getAll(k)) + locker.withWriteLock { + kvCache.set(k, parentRepo.getAll(k)) + } } protected open suspend fun actualizeAll() { - kvCache.actualizeAll(parentRepo) + locker.withWriteLock { + kvCache.actualizeAll(parentRepo) + } } override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult { @@ -109,45 +114,55 @@ open class FullReadKeyValuesCacheRepo( } fun ReadKeyValuesRepo.cached( - kvCache: FullKVCache> -) = FullReadKeyValuesCacheRepo(this, kvCache) + kvCache: KeyValueRepo>, + locker: SmartRWLocker = SmartRWLocker(), +) = FullReadKeyValuesCacheRepo(this, kvCache, locker) open class FullWriteKeyValuesCacheRepo( parentRepo: WriteKeyValuesRepo, - protected open val kvCache: FullKVCache>, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) + protected open val kvCache: KeyValueRepo>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + protected val locker: SmartRWLocker = SmartRWLocker(), ) : WriteKeyValuesRepo by parentRepo, FullCacheRepo { protected val onNewJob = parentRepo.onNewValue.onEach { - kvCache.set( - it.first, - kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second) - ) + locker.withWriteLock { + 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 - ) + locker.withWriteLock { + kvCache.set( + it.first, + kvCache.get(it.first)?.minus(it.second) ?: return@onEach + ) + } }.launchIn(scope) override suspend fun invalidate() { - kvCache.clear() + locker.withWriteLock { + kvCache.clear() + } } } fun WriteKeyValuesRepo.caching( - kvCache: FullKVCache>, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = FullWriteKeyValuesCacheRepo(this, kvCache, scope) + kvCache: KeyValueRepo>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + locker: SmartRWLocker = SmartRWLocker(), +) = FullWriteKeyValuesCacheRepo(this, kvCache, scope, locker) open class FullKeyValuesCacheRepo( protected open val parentRepo: KeyValuesRepo, - kvCache: FullKVCache>, + kvCache: KeyValueRepo>, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), - skipStartInvalidate: Boolean = false -) : FullWriteKeyValuesCacheRepo(parentRepo, kvCache, scope), + skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker(), +) : FullWriteKeyValuesCacheRepo(parentRepo, kvCache, scope, locker), KeyValuesRepo, - ReadKeyValuesRepo by FullReadKeyValuesCacheRepo(parentRepo, kvCache) { + ReadKeyValuesRepo by FullReadKeyValuesCacheRepo(parentRepo, kvCache, locker) { init { if (!skipStartInvalidate) { scope.launchSafelyWithoutExceptions { invalidate() } @@ -163,7 +178,9 @@ open class FullKeyValuesCacheRepo( } override suspend fun invalidate() { - kvCache.actualizeAll(parentRepo) + locker.withWriteLock { + kvCache.actualizeAll(parentRepo) + } } override suspend fun removeWithValue(v: Value) { @@ -172,12 +189,16 @@ open class FullKeyValuesCacheRepo( } fun KeyValuesRepo.fullyCached( - kvCache: FullKVCache> = FullKVCache(), - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = FullKeyValuesCacheRepo(this, kvCache, scope) + kvCache: KeyValueRepo> = MapKeyValueRepo(), + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker(), +) = FullKeyValuesCacheRepo(this, kvCache, scope, skipStartInvalidate, locker) @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) fun KeyValuesRepo.caching( - kvCache: FullKVCache>, - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) = FullKeyValuesCacheRepo(this, kvCache, scope) + kvCache: KeyValueRepo>, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + skipStartInvalidate: Boolean = false, + locker: SmartRWLocker = SmartRWLocker(), +) = FullKeyValuesCacheRepo(this, kvCache, scope, skipStartInvalidate, locker) diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt index eb4db819b5c..4ae058eb643 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt @@ -1,16 +1,8 @@ package dev.inmo.micro_utils.repos.cache.util -import dev.inmo.micro_utils.pagination.FirstPagePagination -import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging -import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging -import dev.inmo.micro_utils.repos.ReadCRUDRepo -import dev.inmo.micro_utils.repos.ReadKeyValueRepo -import dev.inmo.micro_utils.repos.ReadKeyValuesRepo -import dev.inmo.micro_utils.repos.cache.cache.KVCache -import dev.inmo.micro_utils.repos.pagination.getAll -import dev.inmo.micro_utils.repos.set +import dev.inmo.micro_utils.repos.* -suspend inline fun KVCache.actualizeAll( +suspend inline fun KeyValueRepo.actualizeAll( clear: Boolean = true, getAll: () -> Map ) { @@ -23,7 +15,7 @@ suspend inline fun KVCache.actualizeAll( ) } -suspend inline fun KVCache.actualizeAll( +suspend inline fun KeyValueRepo.actualizeAll( repo: ReadKeyValueRepo, clear: Boolean = true, ) { @@ -32,7 +24,7 @@ suspend inline fun KVCache.actualizeAll( } } -suspend inline fun KVCache>.actualizeAll( +suspend inline fun KeyValueRepo>.actualizeAll( repo: ReadKeyValuesRepo, clear: Boolean = true, ) { @@ -41,7 +33,7 @@ suspend inline fun KVCache>.actualizeAll( } } -suspend inline fun KVCache.actualizeAll( +suspend inline fun KeyValueRepo.actualizeAll( repo: ReadCRUDRepo, clear: Boolean = true, ) { diff --git a/repos/common/src/androidMain/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt b/repos/common/src/androidMain/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt index 34cf52aa862..d967cee32ab 100644 --- a/repos/common/src/androidMain/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt +++ b/repos/common/src/androidMain/kotlin/dev/inmo/micro_utils/repos/keyvalue/KeyValueStore.kt @@ -52,7 +52,9 @@ class KeyValueStore internal constructor ( } } - override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String) { + override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) { + sp ?: return + key ?: return val value = sp.all[key] cachedData ?: return if (value != null) { diff --git a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapCRUDRepo.kt b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapCRUDRepo.kt index 981b78b654d..c9aa60c63f4 100644 --- a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapCRUDRepo.kt +++ b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapCRUDRepo.kt @@ -1,38 +1,69 @@ package dev.inmo.micro_utils.repos +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.pagination.* import kotlinx.coroutines.flow.* +/** + * [Map]-based [ReadMapCRUDRepo]. All internal operations will be locked with [locker] (mostly with + * [SmartRWLocker.withReadAcquire]) + * + * **Warning**: It is not recommended to use constructor with both [Map] and [SmartRWLocker]. Besides, in case + * you are using your own [Map] as a [map] you should be careful with operations on this [map] + */ class ReadMapCRUDRepo( - private val map: Map = emptyMap() + private val map: Map = emptyMap(), + private val locker: SmartRWLocker = SmartRWLocker() ) : ReadCRUDRepo { override suspend fun getByPagination(pagination: Pagination): PaginationResult { - return map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull { - map[it] - }.createPaginationResult( - pagination, - count() - ) + return locker.withReadAcquire { + map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull { + map[it] + }.createPaginationResult( + pagination, + count() + ) + } } override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult { - return map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult( - pagination, - count() - ) + return locker.withReadAcquire { + map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult( + pagination, + count() + ) + } } - override suspend fun getById(id: IdType): ObjectType? = map[id] + override suspend fun getById(id: IdType): ObjectType? = locker.withReadAcquire { + map[id] + } - override suspend fun contains(id: IdType): Boolean = map.containsKey(id) + override suspend fun contains(id: IdType): Boolean = locker.withReadAcquire { + map.containsKey(id) + } - override suspend fun getAll(): Map = map.toMap() + override suspend fun getAll(): Map = locker.withReadAcquire { + map.toMap() + } - override suspend fun count(): Long = map.size.toLong() + override suspend fun count(): Long = locker.withReadAcquire { + map.size.toLong() + } } +/** + * [MutableMap]-based [WriteMapCRUDRepo]. All internal operations will be locked with [locker] (mostly with + * [SmartRWLocker.withWriteLock]) + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [map] you should be careful with operations on this [map] + */ abstract class WriteMapCRUDRepo( - protected val map: MutableMap = mutableMapOf() + protected val map: MutableMap = mutableMapOf(), + protected val locker: SmartRWLocker = SmartRWLocker() ) : WriteCRUDRepo { protected val _newObjectsFlow: MutableSharedFlow = MutableSharedFlow() override val newObjectsFlow: Flow = _newObjectsFlow.asSharedFlow() @@ -45,21 +76,25 @@ abstract class WriteMapCRUDRepo( protected abstract suspend fun createObject(newValue: InputValueType): Pair override suspend fun create(values: List): List { - return values.map { - val (id, newObject) = createObject(it) - map[id] = newObject - newObject.also { _ -> - _newObjectsFlow.emit(newObject) + return locker.withWriteLock { + values.map { + val (id, newObject) = createObject(it) + map[id] = newObject + newObject.also { _ -> + _newObjectsFlow.emit(newObject) + } } } } override suspend fun update(id: IdType, value: InputValueType): ObjectType? { - val newValue = updateObject(value, id, map[id] ?: return null) + return locker.withWriteLock { + val newValue = updateObject(value, id, map[id] ?: return@withWriteLock null) - return newValue.also { - map[id] = it - _updatedObjectsFlow.emit(it) + newValue.also { + map[id] = it + _updatedObjectsFlow.emit(it) + } } } @@ -68,24 +103,40 @@ abstract class WriteMapCRUDRepo( } override suspend fun deleteById(ids: List) { - ids.forEach { - map.remove(it) ?.also { _ -> _deletedObjectsIdsFlow.emit(it) } + locker.withWriteLock { + ids.forEach { + map.remove(it) ?.also { _ -> _deletedObjectsIdsFlow.emit(it) } + } } } } +/** + * [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker] + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [map] you should be careful with operations on this [map] + */ abstract class MapCRUDRepo( - map: MutableMap + map: MutableMap, + locker: SmartRWLocker = SmartRWLocker() ) : CRUDRepo, - ReadCRUDRepo by ReadMapCRUDRepo(map), - WriteMapCRUDRepo(map) + ReadCRUDRepo by ReadMapCRUDRepo(map, locker), + WriteMapCRUDRepo(map, locker) +/** + * [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker] + * + * **Warning**: Besides, in case you are using your own [MutableMap] as a [map] you should be careful with operations + * on this [map] + */ fun MapCRUDRepo( map: MutableMap, updateCallback: suspend MutableMap.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType, + locker: SmartRWLocker = SmartRWLocker(), createCallback: suspend MutableMap.(newValue: InputValueType) -> Pair -) = object : MapCRUDRepo(map) { +) = object : MapCRUDRepo(map, locker) { override suspend fun updateObject( newValue: InputValueType, id: IdType, @@ -95,12 +146,23 @@ fun MapCRUDRepo( override suspend fun createObject(newValue: InputValueType): Pair = map.createCallback(newValue) } +/** + * [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker] + */ fun MapCRUDRepo( updateCallback: suspend MutableMap.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType, + locker: SmartRWLocker = SmartRWLocker(), createCallback: suspend MutableMap.(newValue: InputValueType) -> Pair -) = MapCRUDRepo(mutableMapOf(), updateCallback, createCallback) +) = MapCRUDRepo(mutableMapOf(), updateCallback, locker, createCallback) +/** + * [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker] + * + * **Warning**: Besides, in case you are using your own [MutableMap] as a [this] you should be careful with operations + * on this [this] + */ fun MutableMap.asCrudRepo( updateCallback: suspend MutableMap.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType, + locker: SmartRWLocker = SmartRWLocker(), createCallback: suspend MutableMap.(newValue: InputValueType) -> Pair -) = MapCRUDRepo(this, updateCallback, createCallback) +) = MapCRUDRepo(this, updateCallback, locker, createCallback) diff --git a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt index d93bbcc1ca8..71ccd98c865 100644 --- a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt +++ b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt @@ -1,5 +1,8 @@ package dev.inmo.micro_utils.repos +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.utils.paginate @@ -7,59 +10,83 @@ import dev.inmo.micro_utils.pagination.utils.reverse import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +/** + * [Map]-based [ReadKeyValueRepo]. All internal operations will be locked with [locker] (mostly with + * [SmartRWLocker.withReadAcquire]) + * + * **Warning**: It is not recommended to use constructor with both [Map] and [SmartRWLocker]. Besides, in case you are + * using your own [Map] as a [map] you should be careful with operations on this [map] + */ class ReadMapKeyValueRepo( - protected val map: Map = emptyMap() + protected val map: Map, + private val locker: SmartRWLocker ) : ReadKeyValueRepo { - override suspend fun get(k: Key): Value? = map[k] + constructor(map: Map = emptyMap()) : this(map, SmartRWLocker()) + + override suspend fun get(k: Key): Value? = locker.withReadAcquire { map[k] } override suspend fun values( pagination: Pagination, reversed: Boolean ): PaginationResult { - val values = map.values - val actualPagination = if (reversed) pagination.reverse(values.size) else pagination - return values.paginate(actualPagination).let { - if (reversed) { - it.copy(results = it.results.reversed()) - } else { - it + return locker.withReadAcquire { + val values = map.values + val actualPagination = if (reversed) pagination.reverse(values.size) else pagination + values.paginate(actualPagination).let { + if (reversed) { + it.copy(results = it.results.reversed()) + } else { + it + } } } } override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult { - val keys = map.keys - val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination - return keys.paginate(actualPagination).let { - if (reversed) { - it.copy(results = it.results.reversed()) - } else { - it + return locker.withReadAcquire { + val keys = map.keys + val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination + keys.paginate(actualPagination).let { + if (reversed) { + it.copy(results = it.results.reversed()) + } else { + it + } } } } override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult { - val keys: List = map.mapNotNull { (k, value) -> if (v == value) k else null } - val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination - return keys.paginate(actualPagination).let { - if (reversed) { - it.copy(results = it.results.reversed()) - } else { - it + return locker.withReadAcquire { + val keys: List = map.mapNotNull { (k, value) -> if (v == value) k else null } + val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination + keys.paginate(actualPagination).let { + if (reversed) { + it.copy(results = it.results.reversed()) + } else { + it + } } } } - override suspend fun getAll(): Map = map.toMap() + override suspend fun getAll(): Map = locker.withReadAcquire { map.toMap() } - override suspend fun contains(key: Key): Boolean = map.containsKey(key) + override suspend fun contains(key: Key): Boolean = locker.withReadAcquire { map.containsKey(key) } - override suspend fun count(): Long = map.size.toLong() + override suspend fun count(): Long = locker.withReadAcquire { map.size.toLong() } } +/** + * [MutableMap]-based [WriteKeyValueRepo]. All internal operations will be locked with [locker] (mostly with + * [SmartRWLocker.withWriteLock]) + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [map] you should be careful with operations on this [map] + */ class WriteMapKeyValueRepo( - private val map: MutableMap = mutableMapOf() + private val map: MutableMap, + private val locker: SmartRWLocker ) : WriteKeyValueRepo { private val _onNewValue: MutableSharedFlow> = MutableSharedFlow() override val onNewValue: Flow> @@ -67,37 +94,56 @@ class WriteMapKeyValueRepo( private val _onValueRemoved: MutableSharedFlow = MutableSharedFlow() override val onValueRemoved: Flow get() = _onValueRemoved + constructor(map: MutableMap = mutableMapOf()) : this(map, SmartRWLocker()) override suspend fun set(toSet: Map) { - map.putAll(toSet) + locker.withWriteLock { map.putAll(toSet) } toSet.forEach { (k, v) -> _onNewValue.emit(k to v) } } override suspend fun unset(toUnset: List) { - toUnset.forEach { k -> - map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) } + locker.withWriteLock { + toUnset.forEach { k -> + map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) } + } } } override suspend fun unsetWithValues(toUnset: List) { - map.mapNotNull { (k, v) -> - k.takeIf { v in toUnset } - }.forEach { - map.remove(it) - _onValueRemoved.emit(it) + locker.withWriteLock { + map.mapNotNull { (k, v) -> + k.takeIf { v in toUnset } + }.forEach { + map.remove(it) + _onValueRemoved.emit(it) + } } } } +/** + * [MutableMap]-based [KeyValueRepo]. All internal operations will be locked with [locker] + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [map] you should be careful with operations on this [map] + */ @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") class MapKeyValueRepo( - private val map: MutableMap = mutableMapOf() + private val map: MutableMap, + private val locker: SmartRWLocker ) : KeyValueRepo, - ReadKeyValueRepo by ReadMapKeyValueRepo(map), - WriteKeyValueRepo by WriteMapKeyValueRepo(map) { + ReadKeyValueRepo by ReadMapKeyValueRepo(map, locker), + WriteKeyValueRepo by WriteMapKeyValueRepo(map, locker) { + constructor(map: MutableMap = mutableMapOf()) : this(map, SmartRWLocker()) override suspend fun clear() { - map.clear() + locker.withWriteLock { map.clear() } } } -fun MutableMap.asKeyValueRepo(): KeyValueRepo = MapKeyValueRepo(this) +/** + * [MutableMap]-based [KeyValueRepo]. All internal operations will be locked with [locker] + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [this] you should be careful with operations on this [this] + */ +fun MutableMap.asKeyValueRepo(locker: SmartRWLocker = SmartRWLocker()): KeyValueRepo = MapKeyValueRepo(this, locker) diff --git a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt index 48acbabd108..c1c61edcc79 100644 --- a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt +++ b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt @@ -1,15 +1,28 @@ package dev.inmo.micro_utils.repos +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.utils.paginate import dev.inmo.micro_utils.pagination.utils.reverse import kotlinx.coroutines.flow.* +/** + * [Map]-based [ReadKeyValuesRepo]. All internal operations will be locked with [locker] (mostly with + * [SmartRWLocker.withReadAcquire]) + * + * **Warning**: It is not recommended to use constructor with both [Map] and [SmartRWLocker]. Besides, in case + * you are using your own [Map] as a [map] you should be careful with operations on this [map] + */ class MapReadKeyValuesRepo( - private val map: Map> = emptyMap() + private val map: Map> = emptyMap(), + private val locker: SmartRWLocker = SmartRWLocker() ) : ReadKeyValuesRepo { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult { - val list = map[k] ?: return emptyPaginationResult() + val list = locker.withReadAcquire { + map[k] ?: return emptyPaginationResult() + } return list.paginate( if (reversed) { @@ -21,7 +34,9 @@ class MapReadKeyValuesRepo( } override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult { - val keys = map.keys + val keys = locker.withReadAcquire { + map.keys + } val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination return keys.paginate(actualPagination).let { if (reversed) { @@ -33,7 +48,9 @@ class MapReadKeyValuesRepo( } override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult { - val keys = map.keys.filter { map[it] ?.contains(v) == true } + val keys = locker.withReadAcquire { + map.keys.filter { map[it] ?.contains(v) == true } + } val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination return keys.paginate(actualPagination).let { if (reversed) { @@ -44,17 +61,25 @@ class MapReadKeyValuesRepo( } } - override suspend fun contains(k: Key): Boolean = map.containsKey(k) + override suspend fun contains(k: Key): Boolean = locker.withReadAcquire { map.containsKey(k) } - override suspend fun contains(k: Key, v: Value): Boolean = map[k] ?.contains(v) == true + override suspend fun contains(k: Key, v: Value): Boolean = locker.withReadAcquire { map[k] ?.contains(v) } == true - override suspend fun count(k: Key): Long = map[k] ?.size ?.toLong() ?: 0L + override suspend fun count(k: Key): Long = locker.withReadAcquire { map[k] ?.size } ?.toLong() ?: 0L - override suspend fun count(): Long = map.size.toLong() + override suspend fun count(): Long = locker.withReadAcquire { map.size }.toLong() } +/** + * [MutableMap]-based [WriteKeyValuesRepo]. All internal operations will be locked with [locker] (mostly with + * [SmartRWLocker.withWriteLock]) + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [map] you should be careful with operations on this [map] + */ class MapWriteKeyValuesRepo( - private val map: MutableMap> = mutableMapOf() + private val map: MutableMap> = mutableMapOf(), + private val locker: SmartRWLocker = SmartRWLocker() ) : WriteKeyValuesRepo { private val _onNewValue: MutableSharedFlow> = MutableSharedFlow() override val onNewValue: Flow> = _onNewValue.asSharedFlow() @@ -64,61 +89,84 @@ class MapWriteKeyValuesRepo( override val onDataCleared: Flow = _onDataCleared.asSharedFlow() override suspend fun add(toAdd: Map>) { - toAdd.keys.forEach { k -> - if (map.getOrPut(k) { mutableListOf() }.addAll(toAdd[k] ?: return@forEach)) { - toAdd[k] ?.forEach { v -> - _onNewValue.emit(k to v) + locker.withWriteLock { + toAdd.keys.forEach { k -> + if (map.getOrPut(k) { mutableListOf() }.addAll(toAdd[k] ?: return@forEach)) { + toAdd[k] ?.forEach { v -> + _onNewValue.emit(k to v) + } } } } } override suspend fun remove(toRemove: Map>) { - toRemove.keys.forEach { k -> - if (map[k] ?.removeAll(toRemove[k] ?: return@forEach) == true) { - toRemove[k] ?.forEach { v -> - _onValueRemoved.emit(k to v) + locker.withWriteLock { + toRemove.keys.forEach { k -> + if (map[k]?.removeAll(toRemove[k] ?: return@forEach) == true) { + toRemove[k]?.forEach { v -> + _onValueRemoved.emit(k to v) + } + } + if (map[k]?.isEmpty() == true) { + map.remove(k) + _onDataCleared.emit(k) } - } - if (map[k] ?.isEmpty() == true) { - map.remove(k) - _onDataCleared.emit(k) } } } override suspend fun removeWithValue(v: Value) { - map.forEach { (k, values) -> - if (values.remove(v)) { - _onValueRemoved.emit(k to v) + locker.withWriteLock { + map.forEach { (k, values) -> + if (values.remove(v)) { + _onValueRemoved.emit(k to v) + } } } } override suspend fun clear(k: Key) { - map.remove(k) ?.also { _onDataCleared.emit(k) } + locker.withWriteLock { + map.remove(k) ?.also { _onDataCleared.emit(k) } + } } override suspend fun clearWithValue(v: Value) { - map.filter { (_, values) -> - values.contains(v) - }.forEach { - map.remove(it.key) ?.onEach { v -> - _onValueRemoved.emit(it.key to v) - } ?.also { _ -> - _onDataCleared.emit(it.key) + locker.withWriteLock { + map.filter { (_, values) -> + values.contains(v) + }.forEach { + map.remove(it.key)?.onEach { v -> + _onValueRemoved.emit(it.key to v) + }?.also { _ -> + _onDataCleared.emit(it.key) + } } } } } +/** + * [MutableMap]-based [KeyValuesRepo]. All internal operations will be locked with [locker] + * + * **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case + * you are using your own [MutableMap] as a [map] you should be careful with operations on this [map] + */ @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") class MapKeyValuesRepo( - private val map: MutableMap> = mutableMapOf() + private val map: MutableMap> = mutableMapOf(), + private val locker: SmartRWLocker = SmartRWLocker() ) : KeyValuesRepo, - ReadKeyValuesRepo by MapReadKeyValuesRepo(map), - WriteKeyValuesRepo by MapWriteKeyValuesRepo(map) + ReadKeyValuesRepo by MapReadKeyValuesRepo(map, locker), + WriteKeyValuesRepo by MapWriteKeyValuesRepo(map, locker) -fun MutableMap>.asKeyValuesRepo(): KeyValuesRepo = MapKeyValuesRepo( - map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap() +/** + * [MutableMap]-based [KeyValuesRepo]. All internal operations will be locked with [locker] + * + * **Warning**: In case you are using your own [MutableMap] as [this] receiver you should be careful with operations on [this] map + */ +fun MutableMap>.asKeyValuesRepo(locker: SmartRWLocker = SmartRWLocker()): KeyValuesRepo = MapKeyValuesRepo( + map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap(), + locker )