diff --git a/CHANGELOG.md b/CHANGELOG.md index 857b59af9c8..6afc4c0a46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.4.31 +* Add subproject `repos.cache` + ## 0.4.30 * `Versions`: diff --git a/repos/cache/build.gradle b/repos/cache/build.gradle new file mode 100644 index 00000000000..a847ec69fcb --- /dev/null +++ b/repos/cache/build.gradle @@ -0,0 +1,17 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppProjectWithSerializationPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api internalProject("micro_utils.repos.common") + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..0648dddb8f6 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt @@ -0,0 +1,42 @@ +package dev.inmo.micro_utils.repos.cache + +import dev.inmo.micro_utils.repos.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +open class KeyValueCacheRepo( + protected val parentRepo: KeyValueRepo, + protected val cachedValuesCount: Int, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : KeyValueRepo by parentRepo { + protected open val cache = mutableMapOf() + protected open val cacheStack = ArrayList(cachedValuesCount) + protected val syncMutex = Mutex() + protected val onNewJob = parentRepo.onNewValue.onEach { putCacheValue(it.first, it.second) }.launchIn(scope) + protected val onRemoveJob = parentRepo.onValueRemoved.onEach { removeCacheValue(it) }.launchIn(scope) + + protected suspend fun putCacheValue(k: Key, v: Value) = syncMutex.withLock { + if (cache.size >= cachedValuesCount) { + val key = cacheStack.removeAt(0) + cache.remove(key) + } + cacheStack.add(k) + cache[k] = v + } + + protected suspend fun removeCacheValue(k: Key) = syncMutex.withLock { + val i = cacheStack.indexOf(k) + if (i >= 0) { + val key = cacheStack.removeAt(i) + cache.remove(key) + } + } + + override suspend fun get(k: Key): Value? = syncMutex.withLock { + cache[k] ?: parentRepo.get(k) ?.also { cache[k] = it } + } + override suspend fun contains(key: Key): Boolean = cache.containsKey(key) || parentRepo.contains(key) +} 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 new file mode 100644 index 00000000000..54f46cc1717 --- /dev/null +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt @@ -0,0 +1,74 @@ +package dev.inmo.micro_utils.repos.cache + +import dev.inmo.micro_utils.pagination.Pagination +import dev.inmo.micro_utils.pagination.PaginationResult +import dev.inmo.micro_utils.pagination.utils.paginate +import dev.inmo.micro_utils.pagination.utils.reverse +import dev.inmo.micro_utils.repos.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +open class KeyValuesCacheRepo( + protected val parentRepo: KeyValuesRepo, + protected val cachedValuesCount: Int, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : KeyValuesRepo by parentRepo { + protected open val cache = mutableMapOf>() + protected open val cacheStack = ArrayList(cachedValuesCount) + protected val syncMutex = Mutex() + protected val onNewJob = parentRepo.onNewValue.onEach { putCacheValue(it.first, it.second) }.launchIn(scope) + protected val onRemoveJob = parentRepo.onValueRemoved.onEach { removeCacheValue(it.first, it.second) }.launchIn(scope) + protected val onDataClearedJob = parentRepo.onDataCleared.onEach { clearCacheValues(it) }.launchIn(scope) + + protected suspend fun putCacheValues(k: Key, v: List) = syncMutex.withLock { + if (cache.size >= cachedValuesCount) { + val key = cacheStack.removeAt(0) + cache.remove(key) + } + cacheStack.add(k) + cache[k] = v + } + protected suspend fun putCacheValue(k: Key, v: Value) = syncMutex.withLock { + cache[k] ?.let { + cache[k] = it + v + } + } ?: putCacheValues(k, listOf(v)) + + protected suspend fun removeCacheValue(k: Key, v: Value) = syncMutex.withLock { + cache[k] ?.let { + val newList = it - v + if (newList.isEmpty()) { + cache.remove(k) + cacheStack.remove(k) + } else { + cache[k] = newList + } + } + } + + protected suspend fun clearCacheValues(k: Key) = syncMutex.withLock { + val i = cacheStack.indexOf(k) + if (i >= 0) { + val key = cacheStack.removeAt(i) + cache.remove(key) + } + } + + override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult { + return cache[k] ?.paginate( + pagination.let { if (reversed) it.reverse(count(k)) else it } + ) ?.let { + if (reversed) it.copy(results = it.results.reversed()) else it + } ?: parentRepo.get(k, pagination, reversed) + } + override suspend fun getAll(k: Key, reversed: Boolean): List { + return cache[k] ?.let { + if (reversed) it.reversed() else it + } ?: parentRepo.getAll(k, reversed) + } + override suspend fun contains(k: Key, v: Value): Boolean = cache[k] ?.contains(v) ?: parentRepo.contains(k, v) + override suspend fun contains(k: Key): Boolean = cache.containsKey(k) || parentRepo.contains(k) +} diff --git a/repos/cache/src/main/AndroidManifest.xml b/repos/cache/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..6abbe185efb --- /dev/null +++ b/repos/cache/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index ea4cd8a08b3..5d0afc1c814 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ String[] includes = [ ":pagination:ktor:server", ":mime_types", ":repos:common", + ":repos:cache", ":repos:exposed", ":repos:inmemory", ":repos:ktor:client",