diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt new file mode 100644 index 00000000000..abc2a78718e --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt @@ -0,0 +1,19 @@ +package dev.inmo.micro_utils.repos.cache.fallback + +import dev.inmo.micro_utils.coroutines.runCatchingSafely +import kotlinx.coroutines.withTimeout + +interface ActionWrapper { + suspend fun wrap(block: suspend () -> T): Result + + class Timeouted(private val timeoutMillis: Long) : ActionWrapper { + override suspend fun wrap(block: suspend () -> T): Result = runCatchingSafely { + withTimeout(timeoutMillis) { + block() + } + } + } + object Direct : ActionWrapper { + override suspend fun wrap(block: suspend () -> T): Result = runCatchingSafely { block() } + } +} 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 new file mode 100644 index 00000000000..cc28b5245d7 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheCRUDRepo.kt @@ -0,0 +1,36 @@ +package dev.inmo.micro_utils.repos.cache.fallback.crud + +import dev.inmo.micro_utils.repos.CRUDRepo +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 + +open class AutoRecacheCRUDRepo( + originalRepo: CRUDRepo, + scope: CoroutineScope, + kvCache: FullKVCache = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + actionWrapper: ActionWrapper = ActionWrapper.Direct, + idGetter: (RegisteredObject) -> Id +) : AutoRecacheReadCRUDRepo( + originalRepo, + scope, + kvCache, + recacheDelay, + actionWrapper, + idGetter +), + WriteCRUDRepo by AutoRecacheWriteCRUDRepo(originalRepo, scope, kvCache, idGetter), + CRUDRepo { + + constructor( + originalRepo: CRUDRepo, + scope: CoroutineScope, + originalCallTimeoutMillis: Long, + kvCache: FullKVCache = FullKVCache(), + 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 new file mode 100644 index 00000000000..0ba56f67e29 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheReadCRUDRepo.kt @@ -0,0 +1,77 @@ +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.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.set +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds + +open class AutoRecacheReadCRUDRepo( + protected val originalRepo: ReadCRUDRepo, + protected val scope: CoroutineScope, + protected val kvCache: FullKVCache = FullKVCache(), + protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds, + protected val actionWrapper: ActionWrapper = ActionWrapper.Direct, + protected val idGetter: (RegisteredObject) -> Id +) : ReadCRUDRepo { + val autoUpdateJob = scope.launch { + while (isActive) { + runCatchingSafely { + kvCache.actualizeAll(originalRepo) + } + + delay(recacheDelay) + } + } + + constructor( + originalRepo: ReadCRUDRepo, + scope: CoroutineScope, + originalCallTimeoutMillis: Long, + kvCache: FullKVCache = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + idGetter: (RegisteredObject) -> Id + ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) + + override suspend fun contains(id: Id): Boolean = actionWrapper.wrap { + originalRepo.contains(id) + }.getOrElse { + kvCache.contains(id) + } + + override suspend fun count(): Long = actionWrapper.wrap { + originalRepo.count() + }.getOrElse { + kvCache.count() + } + + override suspend fun getByPagination( + pagination: Pagination + ): PaginationResult = actionWrapper.wrap { + originalRepo.getByPagination(pagination) + }.getOrNull() ?.also { + it.results.forEach { + kvCache.set(idGetter(it), it) + } + } ?: kvCache.values(pagination) + + override suspend fun getIdsByPagination( + pagination: Pagination + ): PaginationResult = actionWrapper.wrap { + originalRepo.getIdsByPagination(pagination) + }.getOrElse { kvCache.keys(pagination) } + + override suspend fun getById(id: Id): RegisteredObject? = actionWrapper.wrap { + originalRepo.getById(id) + }.getOrNull() ?.also { + kvCache.set(idGetter(it), it) + } ?: kvCache.get(id) +} 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 new file mode 100644 index 00000000000..19632663e21 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/crud/AutoRecacheWriteCRUDRepo.kt @@ -0,0 +1,60 @@ +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.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 idGetter: (RegisteredObject) -> Id +) : WriteCRUDRepo { + override val deletedObjectsIdsFlow: Flow + get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged() + override val newObjectsFlow: Flow + get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged() + override val updatedObjectsFlow: Flow + get() = originalRepo.updatedObjectsFlow + + private val onRemovingUpdatesListeningJob = originalRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) { + kvCache.unset(it) + } + + private val onNewAndUpdatedObjectsListeningJob = merge( + originalRepo.newObjectsFlow, + originalRepo.updatedObjectsFlow, + ).subscribeSafelyWithoutExceptions(scope) { + kvCache.set(idGetter(it), it) + } + + override suspend fun update( + values: List> + ): List = originalRepo.update(values).onEach { + kvCache.set(idGetter(it), it) + } + + override suspend fun update( + id: Id, + value: InputObject + ): RegisteredObject? = originalRepo.update(id, value) ?.also { + kvCache.set(idGetter(it), it) + } + + override suspend fun deleteById(ids: List) = originalRepo.deleteById(ids).also { + kvCache.unset(ids) + } + + override suspend fun create(values: List): List = originalRepo.create(values).onEach { + kvCache.set(idGetter(it), it) + } +} 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 new file mode 100644 index 00000000000..e1864829e3c --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheKeyValueRepo.kt @@ -0,0 +1,42 @@ +package dev.inmo.micro_utils.repos.cache.fallback.keyvalue + +import dev.inmo.micro_utils.repos.KeyValueRepo +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 + +open class AutoRecacheKeyValueRepo( + override val originalRepo: KeyValueRepo, + scope: CoroutineScope, + kvCache: FullKVCache = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + actionWrapper: ActionWrapper = ActionWrapper.Direct, + idGetter: (RegisteredObject) -> Id +) : AutoRecacheReadKeyValueRepo ( + originalRepo, + scope, + kvCache, + recacheDelay, + actionWrapper, + idGetter +), + WriteKeyValueRepo by AutoRecacheWriteKeyValueRepo(originalRepo, scope, kvCache), + KeyValueRepo { + + constructor( + originalRepo: KeyValueRepo, + scope: CoroutineScope, + originalCallTimeoutMillis: Long, + kvCache: FullKVCache = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + idGetter: (RegisteredObject) -> Id + ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) + + override suspend fun unsetWithValues(toUnset: List) = originalRepo.unsetWithValues( + toUnset + ).also { + kvCache.unsetWithValues(toUnset) + } +} 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 new file mode 100644 index 00000000000..58d90ca310e --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheReadKeyValueRepo.kt @@ -0,0 +1,87 @@ +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.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.set +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +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 recacheDelay: Long = 60.seconds.inWholeMilliseconds, + protected val actionWrapper: ActionWrapper = ActionWrapper.Direct, + protected val idGetter: (RegisteredObject) -> Id +) : ReadKeyValueRepo { + val autoUpdateJob = scope.launch { + while (isActive) { + runCatchingSafely { + kvCache.actualizeAll(originalRepo) + } + + delay(recacheDelay) + } + } + + constructor( + originalRepo: ReadKeyValueRepo, + scope: CoroutineScope, + originalCallTimeoutMillis: Long, + kvCache: FullKVCache = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + idGetter: (RegisteredObject) -> Id + ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) + + override suspend fun contains(key: Id): Boolean = actionWrapper.wrap { + originalRepo.contains(key) + }.getOrElse { + kvCache.contains(key) + } + + override suspend fun count(): Long = actionWrapper.wrap { + originalRepo.count() + }.getOrElse { + kvCache.count() + } + + override suspend fun get(k: Id): RegisteredObject? = actionWrapper.wrap { + originalRepo.get(k) + }.getOrNull() ?.also { + kvCache.set(idGetter(it), it) + } ?: kvCache.get(k) + + override suspend fun values( + pagination: Pagination, + reversed: Boolean + ): PaginationResult = actionWrapper.wrap { + originalRepo.values(pagination, reversed) + }.getOrNull() ?.also { + it.results.forEach { + kvCache.set(idGetter(it), it) + } + } ?: kvCache.values(pagination, reversed) + + override suspend fun keys( + pagination: Pagination, + reversed: Boolean + ): PaginationResult = actionWrapper.wrap { + originalRepo.keys(pagination, reversed) + }.getOrElse { kvCache.keys(pagination, reversed) } + + override suspend fun keys( + v: RegisteredObject, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = actionWrapper.wrap { + originalRepo.keys(v, pagination, reversed) + }.getOrElse { kvCache.keys(v, pagination, reversed) } +} 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 new file mode 100644 index 00000000000..ca91a0a19f2 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalue/AutoRecacheWriteKeyValueRepo.kt @@ -0,0 +1,49 @@ +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.set +import dev.inmo.micro_utils.repos.unset +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged + +open class AutoRecacheWriteKeyValueRepo( + protected val originalRepo: WriteKeyValueRepo, + protected val scope: CoroutineScope, + protected val kvCache: FullKVCache = FullKVCache() +) : WriteKeyValueRepo { + override val onValueRemoved: Flow + get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged() + + override val onNewValue: Flow> + get() = (originalRepo.onNewValue + kvCache.onNewValue).distinctUntilChanged() + + private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) { + kvCache.unset(it) + } + + private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) { + kvCache.set(it.first, it.second) + } + + override suspend fun unsetWithValues(toUnset: List) = originalRepo.unsetWithValues( + toUnset + ).also { + kvCache.unsetWithValues(toUnset) + } + + override suspend fun unset(toUnset: List) = originalRepo.unset( + toUnset + ).also { + kvCache.unset(toUnset) + } + + override suspend fun set(toSet: Map) = originalRepo.set( + toSet + ).also { + kvCache.set(toSet) + } +} 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 new file mode 100644 index 00000000000..8527e89ecf5 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheKeyValueRepo.kt @@ -0,0 +1,40 @@ +package dev.inmo.micro_utils.repos.cache.fallback.keyvalues + +import dev.inmo.micro_utils.repos.KeyValuesRepo +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 + +open class AutoRecacheKeyValuesRepo( + override val originalRepo: KeyValuesRepo, + scope: CoroutineScope, + kvCache: FullKVCache> = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + actionWrapper: ActionWrapper = ActionWrapper.Direct, + idGetter: (RegisteredObject) -> Id +) : AutoRecacheReadKeyValuesRepo ( + originalRepo, + scope, + kvCache, + recacheDelay, + actionWrapper, + idGetter +), + WriteKeyValuesRepo by AutoRecacheWriteKeyValuesRepo(originalRepo, scope, kvCache), + KeyValuesRepo { + + constructor( + originalRepo: KeyValuesRepo, + scope: CoroutineScope, + originalCallTimeoutMillis: Long, + kvCache: FullKVCache> = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + idGetter: (RegisteredObject) -> Id + ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) + + override suspend fun clearWithValue(v: RegisteredObject) { + super.clearWithValue(v) + } +} 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 new file mode 100644 index 00000000000..918f7083491 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheReadKeyValuesRepo.kt @@ -0,0 +1,138 @@ +package dev.inmo.micro_utils.repos.cache.fallback.keyvalues + +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.pagination.changeResultsUnchecked +import dev.inmo.micro_utils.pagination.createPaginationResult +import dev.inmo.micro_utils.pagination.emptyPaginationResult +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.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.set +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +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 recacheDelay: Long = 60.seconds.inWholeMilliseconds, + protected val actionWrapper: ActionWrapper = ActionWrapper.Direct, + protected val idGetter: (RegisteredObject) -> Id +) : ReadKeyValuesRepo { + val autoUpdateJob = scope.launch { + while (isActive) { + runCatchingSafely { + kvCache.actualizeAll(originalRepo) + } + + delay(recacheDelay) + } + } + + constructor( + originalRepo: ReadKeyValuesRepo, + scope: CoroutineScope, + originalCallTimeoutMillis: Long, + kvCache: FullKVCache> = FullKVCache(), + recacheDelay: Long = 60.seconds.inWholeMilliseconds, + idGetter: (RegisteredObject) -> Id + ) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter) + + override suspend fun contains(k: Id): Boolean = actionWrapper.wrap { + originalRepo.contains(k) + }.getOrElse { + kvCache.contains(k) + } + + override suspend fun count(): Long = actionWrapper.wrap { + originalRepo.count() + }.getOrElse { + kvCache.count() + } + + override suspend fun keys( + v: RegisteredObject, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = actionWrapper.wrap { + originalRepo.keys(v, pagination, reversed) + }.getOrElse { + val results = mutableListOf() + + val toSkip = pagination.firstIndex + var count = 0 + + doForAllWithNextPaging { + kvCache.keys(pagination, reversed).also { + it.results.forEach { + if (kvCache.get(it) ?.contains(v) == true) { + count++ + if (count < toSkip || results.size >= pagination.size) { + return@forEach + } else { + results.add(it) + } + } + } + } + } + + return@getOrElse results.createPaginationResult( + pagination, + count.toLong() + ) + } + + override suspend fun keys( + pagination: Pagination, + reversed: Boolean + ): PaginationResult = actionWrapper.wrap { + originalRepo.keys(pagination, reversed) + }.getOrElse { kvCache.keys(pagination, reversed) } + + override suspend fun get( + k: Id, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = actionWrapper.wrap { + originalRepo.get(k, pagination, reversed) + }.getOrNull() ?.also { + it.results.forEach { + kvCache.set(k, ((kvCache.get(k) ?: return@also) + it).distinct()) + } + } ?: kvCache.get(k) ?.run { + paginate(pagination.optionallyReverse(size, reversed)).let { + if (reversed) { + it.changeResultsUnchecked( + it.results.reversed() + ) + } else { + it + } + } + } ?: emptyPaginationResult() + + override suspend fun count(k: Id): Long = actionWrapper.wrap { + originalRepo.count(k) + }.getOrElse { + kvCache.get(k) ?.size ?.toLong() ?: 0L + } + + override suspend fun contains(k: Id, v: RegisteredObject): Boolean { + return (actionWrapper.wrap { + originalRepo.contains(k, v) + }.getOrNull() ?.also { + kvCache.set(k, ((kvCache.get(k) ?: return@also) + v).distinct()) + }) ?: (kvCache.get(k) ?.contains(v) == true) + } +} 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 new file mode 100644 index 00000000000..4c57b3b7f5d --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/keyvalues/AutoRecacheWriteKeyValuesRepo.kt @@ -0,0 +1,81 @@ +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.set +import dev.inmo.micro_utils.repos.unset +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged + +open class AutoRecacheWriteKeyValuesRepo( + protected val originalRepo: WriteKeyValuesRepo, + protected val scope: CoroutineScope, + protected val kvCache: FullKVCache> = FullKVCache() +) : WriteKeyValuesRepo { + override val onValueRemoved: Flow> + get() = originalRepo.onValueRemoved + + override val onNewValue: Flow> + get() = originalRepo.onNewValue + override val onDataCleared: Flow + get() = (originalRepo.onDataCleared + kvCache.onValueRemoved).distinctUntilChanged() + + private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) { + kvCache.unset(it) + } + + private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) { + kvCache.set( + it.first, + (kvCache.get( + it.first + ) ?: return@subscribeSafelyWithoutExceptions) - it.second + ) + } + + private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) { + kvCache.set( + it.first, + (kvCache.get( + it.first + ) ?: return@subscribeSafelyWithoutExceptions) + it.second + ) + } + + override suspend fun clearWithValue(v: RegisteredObject) { + originalRepo.clearWithValue(v) + doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) { + kvCache.keys(it).also { + it.results.forEach { id -> + kvCache.get(id) ?.takeIf { it.contains(v) } ?.let { + kvCache.unset(id) + } + } + } + } + } + + override suspend fun clear(k: Id) { + originalRepo.clear(k) + kvCache.unset(k) + } + + override suspend fun remove(toRemove: Map>) { + originalRepo.remove(toRemove) + toRemove.forEach { (k, v) -> + kvCache.set(k, (kvCache.get(k) ?: return@forEach) - v) + } + } + + override suspend fun add(toAdd: Map>) { + originalRepo.add(toAdd) + toAdd.forEach { (k, v) -> + kvCache.set(k, (kvCache.get(k) ?: return@forEach) + v) + } + } +}