mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-30 19:50:31 +00:00 
			
		
		
		
	Compare commits
	
		
			21 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 605fc3cff9 | |||
| 12cd6f48f8 | |||
| f09d92be32 | |||
| 6245b36bdb | |||
| 54cc353bcc | |||
| b7abba099c | |||
| c5dbd10335 | |||
| a44e3e953d | |||
| ee2521cb01 | |||
| 4625dfb857 | |||
| b9a2653066 | |||
| fcaa327660 | |||
| 496117d517 | |||
| 8ce7d37b72 | |||
| 46c89c48a9 | |||
| bad9a53fdb | |||
| 0bce7bd60a | |||
| 2f70a1cfb4 | |||
| bfb6e738ee | |||
| c7ad9aae07 | |||
| fecd719239 | 
							
								
								
									
										45
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,50 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 0.22.9 | ||||
|  | ||||
| * `Repos`: | ||||
|   * `Cache`: | ||||
|     * Add direct caching repos | ||||
|  | ||||
| ## 0.22.8 | ||||
|  | ||||
| * `Common`: | ||||
|   * Add `List.breakAsPairs` extension | ||||
|   * Add `Sequence.padWith`/`Sequence.padStart`/`Sequence.padEnd` and `List.padWith`/`List.padStart`/`List.padEnd` extensions | ||||
|  | ||||
| ## 0.22.7 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Kotlin`: `2.0.20` -> `2.0.21` | ||||
|   * `Compose`: `1.7.0-rc01` -> `1.7.0` | ||||
| * `KSP`: | ||||
|   * `Sealed`: | ||||
|     * Change package of `GenerateSealedWorkaround`. Migration: replace `dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround` -> `dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround` | ||||
|  | ||||
| ## 0.22.6 | ||||
|  | ||||
| * `KSP`: | ||||
|   * `Generator`: | ||||
|     * Add extension `KSClassDeclaration.buildSubFileName` | ||||
|     * Add extension `KSClassDeclaration.companion` | ||||
|     * Add extension `KSClassDeclaration.resolveSubclasses` | ||||
|   * `Sealed`: | ||||
|     * Improvements | ||||
|  | ||||
| ## 0.22.5 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Compose`: `1.7.0-beta02` -> `1.7.0-rc01` | ||||
|   * `SQLite`: `3.46.1.2` -> `3.46.1.3` | ||||
|   * `AndroidXFragment`: `1.8.3` -> `1.8.4` | ||||
| * `Common`: | ||||
|   * Add extension `withReplacedAt`/`withReplaced` ([#489](https://github.com/InsanusMokrassar/MicroUtils/issues/489)) | ||||
| * `Coroutines`: | ||||
|   * Add extension `Flow.debouncedBy` | ||||
| * `Ktor`: | ||||
|   * `Server`: | ||||
|     * Add `KtorApplicationConfigurator.Routing.Static` as solution for [#488](https://github.com/InsanusMokrassar/MicroUtils/issues/488) | ||||
|  | ||||
| ## 0.22.4 | ||||
|  | ||||
| * `Versions`: | ||||
|   | ||||
| @@ -0,0 +1,13 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| fun <T> List<T>.breakAsPairs(): List<Pair<T, T>> { | ||||
|     val result = mutableListOf<Pair<T, T>>() | ||||
|  | ||||
|     for (i in 0 until size - 1) { | ||||
|         val first = get(i) | ||||
|         val second = get(i + 1) | ||||
|         result.add(first to second) | ||||
|     } | ||||
|  | ||||
|     return result | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequence<T>): Sequence<T> { | ||||
|     var result = this | ||||
|     while (result.count() < size) { | ||||
|         result = inserter(result) | ||||
|     } | ||||
|     return result | ||||
| } | ||||
|  | ||||
| inline fun <T> Sequence<T>.padEnd(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { it + padBlock(it.count()) } | ||||
|  | ||||
| inline fun <T> Sequence<T>.padEnd(size: Int, o: T) = padEnd(size) { o } | ||||
|  | ||||
| inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<T> { | ||||
|     var result = this | ||||
|     while (result.size < size) { | ||||
|         result = inserter(result) | ||||
|     } | ||||
|     return result | ||||
| } | ||||
| inline fun <T> List<T>.padEnd(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padEnd(size, padBlock).toList() | ||||
|  | ||||
| inline fun <T> List<T>.padEnd(size: Int, o: T): List<T> = asSequence().padEnd(size, o).toList() | ||||
|  | ||||
| inline fun <T> Sequence<T>.padStart(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { sequenceOf(padBlock(it.count())) + it } | ||||
|  | ||||
| inline fun <T> Sequence<T>.padStart(size: Int, o: T) = padStart(size) { o } | ||||
|  | ||||
| inline fun <T> List<T>.padStart(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padStart(size, padBlock).toList() | ||||
|  | ||||
| inline fun <T> List<T>.padStart(size: Int, o: T): List<T> = asSequence().padStart(size, o).toList() | ||||
| @@ -0,0 +1,5 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| fun <T> Iterable<T>.withReplacedAt(i: Int, block: (T) -> T): List<T> = take(i) + block(elementAt(i)) + drop(i + 1) | ||||
| fun <T> Iterable<T>.withReplaced(t: T, block: (T) -> T): List<T> = withReplacedAt(indexOf(t), block) | ||||
|  | ||||
| @@ -0,0 +1,21 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import kotlin.test.Test | ||||
| import kotlin.test.assertEquals | ||||
|  | ||||
| class WithReplacedTest { | ||||
|     @Test | ||||
|     fun testReplaced() { | ||||
|         val data = 0 until 10 | ||||
|         val testData = Int.MAX_VALUE | ||||
|  | ||||
|         for (i in 0 until data.last) { | ||||
|             val withReplaced = data.withReplacedAt(i) { | ||||
|                 testData | ||||
|             } | ||||
|             val dataAsMutableList = data.toMutableList() | ||||
|             dataAsMutableList[i] = testData | ||||
|             assertEquals(withReplaced, dataAsMutableList.toList()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.Job | ||||
| import kotlinx.coroutines.async | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.flow.* | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
| import kotlin.jvm.JvmInline | ||||
| import kotlin.time.Duration | ||||
|  | ||||
| @JvmInline | ||||
| private value class DebouncedByData<T>( | ||||
|     val millisToData: Pair<Long, T> | ||||
| ) | ||||
|  | ||||
| fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): Flow<T> = channelFlow { | ||||
|     val jobs = mutableMapOf<Any?, Job>() | ||||
|     val mutex = Mutex() | ||||
|     subscribe(this) { | ||||
|         mutex.withLock { | ||||
|             val marker = markerFactory(it) | ||||
|             lateinit var job: Job | ||||
|             job = async { | ||||
|                 delay(timeout(it)) | ||||
|                 mutex.withLock { | ||||
|                     if (jobs[marker] === job) { | ||||
|                         this@channelFlow.send(it) | ||||
|                         jobs.remove(marker) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             jobs[marker] ?.cancel() | ||||
|             jobs[marker] = job | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <T> Flow<T>.debouncedBy(timeout: Long, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout }, markerFactory) | ||||
| fun <T> Flow<T>.debouncedBy(timeout: Duration, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout.inWholeMilliseconds }, markerFactory) | ||||
							
								
								
									
										42
									
								
								coroutines/src/commonTest/kotlin/DebouncedByTests.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								coroutines/src/commonTest/kotlin/DebouncedByTests.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import dev.inmo.micro_utils.coroutines.debouncedBy | ||||
| import kotlinx.coroutines.flow.asFlow | ||||
| import kotlinx.coroutines.test.runTest | ||||
| import kotlin.test.Test | ||||
| import kotlin.test.assertEquals | ||||
| import kotlin.test.assertTrue | ||||
|  | ||||
| class DebouncedByTests { | ||||
|     @Test | ||||
|     fun testThatParallelDebouncingWorksCorrectly() = runTest { | ||||
|         val dataToMarkerFactories = listOf( | ||||
|             1 to 0, | ||||
|             2 to 1, | ||||
|             3 to 2, | ||||
|             4 to 0, | ||||
|             5 to 1, | ||||
|             6 to 2, | ||||
|             7 to 0, | ||||
|             8 to 1, | ||||
|             9 to 2, | ||||
|         ) | ||||
|  | ||||
|         val collected = mutableListOf<Int>() | ||||
|  | ||||
|         dataToMarkerFactories.asFlow().debouncedBy(10L) { | ||||
|             it.second | ||||
|         }.collect { | ||||
|             when (it.second) { | ||||
|                 0 -> assertEquals(7, it.first) | ||||
|                 1 -> assertEquals(8, it.first) | ||||
|                 2 -> assertEquals(9, it.first) | ||||
|                 else -> error("wtf") | ||||
|             } | ||||
|             collected.add(it.first) | ||||
|         } | ||||
|  | ||||
|         val expectedList = listOf(7, 8, 9) | ||||
|         assertEquals(expectedList, collected) | ||||
|         assertTrue { collected.containsAll(expectedList) } | ||||
|         assertTrue { expectedList.containsAll(collected) } | ||||
|     } | ||||
| } | ||||
| @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 | ||||
| # Project data | ||||
|  | ||||
| group=dev.inmo | ||||
| version=0.22.4 | ||||
| android_code_version=270 | ||||
| version=0.22.9 | ||||
| android_code_version=275 | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| [versions] | ||||
|  | ||||
| kt = "2.0.20" | ||||
| kt = "2.0.21" | ||||
| kt-serialization = "1.7.3" | ||||
| kt-coroutines = "1.9.0" | ||||
|  | ||||
| kslog = "1.3.6" | ||||
|  | ||||
| jb-compose = "1.7.0-beta02" | ||||
| jb-compose = "1.7.0" | ||||
| jb-exposed = "0.55.0" | ||||
| jb-dokka = "1.9.20" | ||||
|  | ||||
| sqlite = "3.46.1.2" | ||||
| sqlite = "3.46.1.3" | ||||
|  | ||||
| korlibs = "5.4.0" | ||||
| uuid = "0.8.4" | ||||
| @@ -23,7 +23,7 @@ koin = "4.0.0" | ||||
|  | ||||
| okio = "3.9.1" | ||||
|  | ||||
| ksp = "2.0.20-1.0.25" | ||||
| ksp = "2.0.21-1.0.25" | ||||
| kotlin-poet = "1.18.1" | ||||
|  | ||||
| versions = "0.51.0" | ||||
| @@ -34,7 +34,7 @@ dexcount = "4.0.0" | ||||
| android-coreKtx = "1.13.1" | ||||
| android-recyclerView = "1.3.2" | ||||
| android-appCompat = "1.7.0" | ||||
| android-fragment = "1.8.3" | ||||
| android-fragment = "1.8.4" | ||||
| android-espresso = "3.6.1" | ||||
| android-test = "1.2.1" | ||||
| android-compose-material3 = "1.3.0" | ||||
|   | ||||
| @@ -0,0 +1,13 @@ | ||||
| package dev.inmo.micro_ksp.generator | ||||
|  | ||||
| import com.google.devtools.ksp.symbol.KSClassDeclaration | ||||
|  | ||||
| val KSClassDeclaration.buildSubFileName: String | ||||
|     get() { | ||||
|         val parentDeclarationCaptured = parentDeclaration | ||||
|         val simpleNameString = simpleName.asString() | ||||
|         return when (parentDeclarationCaptured) { | ||||
|             is KSClassDeclaration -> parentDeclarationCaptured.buildSubFileName | ||||
|             else -> "" | ||||
|         } + simpleNameString | ||||
|     } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package dev.inmo.micro_ksp.generator | ||||
|  | ||||
| import com.google.devtools.ksp.symbol.KSClassDeclaration | ||||
|  | ||||
| val KSClassDeclaration.companion | ||||
|     get() = declarations.firstNotNullOfOrNull { | ||||
|         (it as? KSClassDeclaration)?.takeIf { it.isCompanionObject } | ||||
|     } | ||||
							
								
								
									
										11
									
								
								ksp/generator/src/main/kotlin/ResolveSubclasses.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ksp/generator/src/main/kotlin/ResolveSubclasses.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| package dev.inmo.micro_ksp.generator | ||||
|  | ||||
| import com.google.devtools.ksp.symbol.KSClassDeclaration | ||||
|  | ||||
| fun KSClassDeclaration.resolveSubclasses(): List<KSClassDeclaration> { | ||||
|     return (getSealedSubclasses().flatMap { | ||||
|         it.resolveSubclasses() | ||||
|     }.ifEmpty { | ||||
|         sequenceOf(this) | ||||
|     }).toList() | ||||
| } | ||||
| @@ -0,0 +1,11 @@ | ||||
| package dev.inmo.micro_utils.ksp.sealed.generator | ||||
|  | ||||
| import com.google.devtools.ksp.KspExperimental | ||||
| import com.google.devtools.ksp.getAnnotationsByType | ||||
| import com.google.devtools.ksp.symbol.KSClassDeclaration | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
| import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround as OldGenerateSealedWorkaround | ||||
|  | ||||
| @OptIn(KspExperimental::class) | ||||
| val KSClassDeclaration.getGenerateSealedWorkaroundAnnotation | ||||
|     get() = (getAnnotationsByType(GenerateSealedWorkaround::class).firstOrNull() ?: getAnnotationsByType(OldGenerateSealedWorkaround::class).firstOrNull()) | ||||
| @@ -15,9 +15,11 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy | ||||
| import com.squareup.kotlinpoet.PropertySpec | ||||
| import com.squareup.kotlinpoet.asTypeName | ||||
| import com.squareup.kotlinpoet.ksp.toClassName | ||||
| import dev.inmo.micro_ksp.generator.buildSubFileName | ||||
| import dev.inmo.micro_ksp.generator.companion | ||||
| import dev.inmo.micro_ksp.generator.findSubClasses | ||||
| import dev.inmo.micro_ksp.generator.writeFile | ||||
| import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
| import java.io.File | ||||
|  | ||||
| class Processor( | ||||
| @@ -51,10 +53,10 @@ class Processor( | ||||
|         ksClassDeclaration: KSClassDeclaration, | ||||
|         resolver: Resolver | ||||
|     ) { | ||||
|         val annotation = ksClassDeclaration.getAnnotationsByType(GenerateSealedWorkaround::class).first() | ||||
|         val annotation = ksClassDeclaration.getGenerateSealedWorkaroundAnnotation | ||||
|         val subClasses = ksClassDeclaration.resolveSubclasses( | ||||
|             searchIn = resolver.getAllFiles(), | ||||
|             allowNonSealed = annotation.includeNonSealedSubTypes | ||||
|             allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false | ||||
|         ).distinct() | ||||
|         val subClassesNames = subClasses.filter { | ||||
|             when (it.classKind) { | ||||
| @@ -93,7 +95,10 @@ class Processor( | ||||
|         ) | ||||
|         addFunction( | ||||
|             FunSpec.builder("values").apply { | ||||
|                 receiver(ClassName(className.packageName, *className.simpleNames.toTypedArray(), "Companion")) | ||||
|                 val companion = ksClassDeclaration.takeIf { it.isCompanionObject } ?.toClassName() | ||||
|                     ?: ksClassDeclaration.companion ?.toClassName() | ||||
|                     ?: ClassName(className.packageName, *className.simpleNames.toTypedArray(), "Companion") | ||||
|                 receiver(companion) | ||||
|                 returns(setType) | ||||
|                 addCode( | ||||
|                     CodeBlock.of( | ||||
| @@ -107,7 +112,9 @@ class Processor( | ||||
|     @OptIn(KspExperimental::class) | ||||
|     override fun process(resolver: Resolver): List<KSAnnotated> { | ||||
|         (resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { | ||||
|             val prefix = it.getAnnotationsByType(GenerateSealedWorkaround::class).first().prefix | ||||
|             val prefix = (it.getGenerateSealedWorkaroundAnnotation) ?.prefix ?.takeIf { | ||||
|                 it.isNotEmpty() | ||||
|             } ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "") | ||||
|             it.writeFile(prefix = prefix, suffix = "SealedWorkaround") { | ||||
|                 FileSpec.builder( | ||||
|                     it.packageName.asString(), | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package dev.inmo.micro_utils.ksp.sealed.generator.test | ||||
|  | ||||
| import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
|  | ||||
| @GenerateSealedWorkaround | ||||
| sealed interface Test { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package dev.inmo.microutils.kps.sealed | ||||
| package dev.inmo.micro_utils.ksp.sealed | ||||
|  | ||||
| @Retention(AnnotationRetention.BINARY) | ||||
| @Target(AnnotationTarget.CLASS) | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| package dev.inmo.microutils.kps.sealed | ||||
|  | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
|  | ||||
| @Deprecated("Replaced", ReplaceWith("GenerateSealedWorkaround", "dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround")) | ||||
| typealias GenerateSealedWorkaround = GenerateSealedWorkaround | ||||
| @@ -1,7 +1,101 @@ | ||||
| package dev.inmo.micro_utils.ktor.server.configurators | ||||
|  | ||||
| import io.ktor.server.application.Application | ||||
| import io.ktor.server.application.* | ||||
| import io.ktor.server.http.content.* | ||||
| import io.ktor.server.plugins.cachingheaders.* | ||||
| import io.ktor.server.plugins.statuspages.* | ||||
| import io.ktor.server.routing.* | ||||
| import io.ktor.server.sessions.* | ||||
| import kotlinx.serialization.Contextual | ||||
| import kotlinx.serialization.Serializable | ||||
| import java.io.File | ||||
|  | ||||
| interface KtorApplicationConfigurator { | ||||
|     @Serializable | ||||
|     class Routing( | ||||
|         private val elements: List<@Contextual Element> | ||||
|     ) : KtorApplicationConfigurator { | ||||
|         fun interface Element { operator fun Route.invoke() } | ||||
|         private val rootInstaller = Element { | ||||
|             elements.forEach { | ||||
|                 it.apply { invoke() } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         override fun Application.configure() { | ||||
|             pluginOrNull(io.ktor.server.routing.Routing) ?.apply { | ||||
|                 rootInstaller.apply { invoke() } | ||||
|             } ?: install(io.ktor.server.routing.Routing) { | ||||
|                 rootInstaller.apply { invoke() } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @param pathToFolder Contains [Pair]s where firsts are paths in urls and seconds are folders file paths | ||||
|          * @param pathToResource Contains [Pair]s where firsts are paths in urls and seconds are packages in resources | ||||
|          */ | ||||
|         class Static( | ||||
|             private val pathToFolder: List<Pair<String, String>> = emptyList(), | ||||
|             private val pathToResource: List<Pair<String, String>> = emptyList(), | ||||
|         ) : Element { | ||||
|             override fun Route.invoke() { | ||||
|                 pathToFolder.forEach { | ||||
|                     staticFiles( | ||||
|                         it.first, | ||||
|                         File(it.second) | ||||
|                     ) | ||||
|                 } | ||||
|                 pathToResource.forEach { | ||||
|                     staticResources( | ||||
|                         it.first, | ||||
|                         it.second | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class StatusPages( | ||||
|         private val elements: List<@Contextual Element> | ||||
|     ) : KtorApplicationConfigurator { | ||||
|         fun interface Element { operator fun StatusPagesConfig.invoke() } | ||||
|  | ||||
|         override fun Application.configure() { | ||||
|             install(StatusPages) { | ||||
|                 elements.forEach { | ||||
|                     it.apply { invoke() } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class Sessions( | ||||
|         private val elements: List<@Contextual Element> | ||||
|     ) : KtorApplicationConfigurator { | ||||
|         fun interface Element { operator fun SessionsConfig.invoke() } | ||||
|  | ||||
|         override fun Application.configure() { | ||||
|             install(Sessions) { | ||||
|                 elements.forEach { | ||||
|                     it.apply { invoke() } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class CachingHeaders( | ||||
|         private val elements: List<@Contextual Element> | ||||
|     ) : KtorApplicationConfigurator { | ||||
|         fun interface Element { operator fun CachingHeadersConfig.invoke() } | ||||
|  | ||||
|         override fun Application.configure() { | ||||
|             install(CachingHeaders) { | ||||
|                 elements.forEach { | ||||
|                     it.apply { invoke() } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun Application.configure() | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,113 @@ | ||||
| package dev.inmo.micro_utils.repos.cache.full.direct | ||||
|  | ||||
| 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.util.ActualizeAllClearMode | ||||
| import dev.inmo.micro_utils.repos.cache.util.actualizeAll | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
|  | ||||
| open class DirectFullReadCRUDCacheRepo<ObjectType, IdType>( | ||||
|     protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>, | ||||
|     protected open val kvCache: KeyValueRepo<IdType, ObjectType>, | ||||
|     protected val locker: SmartRWLocker = SmartRWLocker(), | ||||
|     protected open val idGetter: (ObjectType) -> IdType | ||||
| ) : ReadCRUDRepo<ObjectType, IdType>, DirectFullCacheRepo { | ||||
|     protected open suspend fun actualizeAll() { | ||||
|         kvCache.actualizeAll(parentRepo, locker = locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = locker.withReadAcquire { | ||||
|         kvCache.values(pagination) | ||||
|     } | ||||
|  | ||||
|     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = locker.withReadAcquire { | ||||
|         kvCache.keys(pagination) | ||||
|     } | ||||
|  | ||||
|     override suspend fun count(): Long = locker.withReadAcquire { | ||||
|         kvCache.count() | ||||
|     } | ||||
|  | ||||
|     override suspend fun contains(id: IdType): Boolean = locker.withReadAcquire { | ||||
|         kvCache.contains(id) | ||||
|     } | ||||
|  | ||||
|     override suspend fun getAll(): Map<IdType, ObjectType> = locker.withReadAcquire { | ||||
|         kvCache.getAll() | ||||
|     } | ||||
|  | ||||
|     override suspend fun getById(id: IdType): ObjectType? = locker.withReadAcquire { | ||||
|         kvCache.get(id) | ||||
|     } | ||||
|  | ||||
|     override suspend fun invalidate() { | ||||
|         actualizeAll() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.directlyCached( | ||||
|     kvCache: KeyValueRepo<IdType, ObjectType>, | ||||
|     locker: SmartRWLocker = SmartRWLocker(), | ||||
|     idGetter: (ObjectType) -> IdType | ||||
| ) = DirectFullReadCRUDCacheRepo(this, kvCache, locker, idGetter) | ||||
|  | ||||
| open class DirectFullCRUDCacheRepo<ObjectType, IdType, InputValueType>( | ||||
|     override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>, | ||||
|     kvCache: KeyValueRepo<IdType, ObjectType>, | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     skipStartInvalidate: Boolean = false, | ||||
|     locker: SmartRWLocker = SmartRWLocker(writeIsLocked = !skipStartInvalidate), | ||||
|     idGetter: (ObjectType) -> IdType | ||||
| ) : DirectFullReadCRUDCacheRepo<ObjectType, IdType>( | ||||
|     parentRepo, | ||||
|     kvCache, | ||||
|     locker, | ||||
|     idGetter | ||||
| ), | ||||
|     WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo( | ||||
|         parentRepo, | ||||
|         kvCache, | ||||
|         scope, | ||||
|         locker, | ||||
|         idGetter | ||||
|     ), | ||||
|     CRUDRepo<ObjectType, IdType, InputValueType> { | ||||
|     init { | ||||
|         if (!skipStartInvalidate) { | ||||
|             scope.launchSafelyWithoutExceptions { | ||||
|                 if (locker.writeMutex.isLocked) { | ||||
|                     initialInvalidate() | ||||
|                 } else { | ||||
|                     invalidate() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected open suspend fun initialInvalidate() { | ||||
|         try { | ||||
|             kvCache.actualizeAll(parentRepo, locker = null) | ||||
|         } finally { | ||||
|             locker.unlockWrite() | ||||
|         } | ||||
|     } | ||||
|     override suspend fun invalidate() { | ||||
|         actualizeAll() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.directFullyCached( | ||||
|     kvCache: KeyValueRepo<IdType, ObjectType> = MapKeyValueRepo(), | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     skipStartInvalidate: Boolean = false, | ||||
|     locker: SmartRWLocker = SmartRWLocker(), | ||||
|     idGetter: (ObjectType) -> IdType | ||||
| ) = DirectFullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, locker, idGetter) | ||||
| @@ -0,0 +1,13 @@ | ||||
| package dev.inmo.micro_utils.repos.cache.full.direct | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.cache.full.FullCacheRepo | ||||
|  | ||||
| /** | ||||
|  * Repos-inheritors MUST realize their methods via next logic: | ||||
|  * | ||||
|  * * Reloading of data in cache must be reactive (e.g. via Flow) or direct mutation methods usage (override set and | ||||
|  * mutate cache inside, for example) | ||||
|  * * All reading methods must take data from cache via synchronization with [dev.inmo.micro_utils.coroutines.SmartRWLocker] | ||||
|  */ | ||||
| interface DirectFullCacheRepo : FullCacheRepo { | ||||
| } | ||||
| @@ -0,0 +1,177 @@ | ||||
| package dev.inmo.micro_utils.repos.cache.full.direct | ||||
|  | ||||
| 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.full.FullKeyValueCacheRepo | ||||
| import dev.inmo.micro_utils.repos.cache.full.FullReadKeyValueCacheRepo | ||||
| import dev.inmo.micro_utils.repos.cache.full.FullWriteKeyValueCacheRepo | ||||
| import dev.inmo.micro_utils.repos.cache.util.actualizeAll | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.onEach | ||||
|  | ||||
| open class DirectFullReadKeyValueCacheRepo<Key, Value>( | ||||
|     protected open val parentRepo: ReadKeyValueRepo<Key, Value>, | ||||
|     protected open val kvCache: KeyValueRepo<Key, Value>, | ||||
|     protected val locker: SmartRWLocker = SmartRWLocker() | ||||
| ) : DirectFullCacheRepo, ReadKeyValueRepo<Key, Value> { | ||||
|     protected open suspend fun actualizeAll() { | ||||
|         kvCache.actualizeAll(parentRepo, locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun get(k: Key): Value? = locker.withReadAcquire { | ||||
|         kvCache.get(k) | ||||
|     } | ||||
|  | ||||
|     override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = locker.withReadAcquire { | ||||
|         kvCache.values(pagination, reversed) | ||||
|     } | ||||
|  | ||||
|     override suspend fun count(): Long = locker.withReadAcquire { | ||||
|         kvCache.count() | ||||
|     } | ||||
|  | ||||
|     override suspend fun contains(key: Key): Boolean = locker.withReadAcquire { | ||||
|         kvCache.contains(key) | ||||
|     } | ||||
|  | ||||
|     override suspend fun getAll(): Map<Key, Value> = locker.withReadAcquire { | ||||
|         kvCache.getAll() | ||||
|     } | ||||
|  | ||||
|     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = locker.withReadAcquire { | ||||
|         kvCache.keys(pagination, reversed) | ||||
|     } | ||||
|  | ||||
|     override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = locker.withReadAcquire { | ||||
|         kvCache.keys(v, pagination, reversed) | ||||
|     } | ||||
|  | ||||
|     override suspend fun invalidate() { | ||||
|         actualizeAll() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <Key, Value> ReadKeyValueRepo<Key, Value>.directlyCached( | ||||
|     kvCache: KeyValueRepo<Key, Value>, | ||||
|     locker: SmartRWLocker = SmartRWLocker() | ||||
| ) = DirectFullReadKeyValueCacheRepo(this, kvCache, locker) | ||||
|  | ||||
| open class DirectFullWriteKeyValueCacheRepo<Key, Value>( | ||||
|     protected open val parentRepo: WriteKeyValueRepo<Key, Value>, | ||||
|     protected open val kvCache: KeyValueRepo<Key, Value>, | ||||
|     protected val locker: SmartRWLocker = SmartRWLocker(), | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
| ) : DirectFullCacheRepo, WriteKeyValueRepo<Key, Value> by parentRepo { | ||||
|     override val onNewValue: Flow<Pair<Key, Value>> | ||||
|         get() = parentRepo.onNewValue | ||||
|     override val onValueRemoved: Flow<Key> | ||||
|         get() = parentRepo.onValueRemoved | ||||
|  | ||||
|     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() { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.clear() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) | ||||
| } | ||||
|  | ||||
| fun <Key, Value> WriteKeyValueRepo<Key, Value>.directlyCached( | ||||
|     kvCache: KeyValueRepo<Key, Value>, | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||
| ) = DirectFullWriteKeyValueCacheRepo(this, kvCache, scope = scope) | ||||
|  | ||||
| open class DirectFullKeyValueCacheRepo<Key, Value>( | ||||
|     override val parentRepo: KeyValueRepo<Key, Value>, | ||||
|     kvCache: KeyValueRepo<Key, Value>, | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     skipStartInvalidate: Boolean = false, | ||||
|     locker: SmartRWLocker = SmartRWLocker(writeIsLocked = !skipStartInvalidate), | ||||
| ) : DirectFullCacheRepo, | ||||
|     KeyValueRepo<Key, Value> , | ||||
|     WriteKeyValueRepo<Key, Value> by DirectFullWriteKeyValueCacheRepo( | ||||
|         parentRepo, | ||||
|         kvCache, | ||||
|         locker, | ||||
|         scope | ||||
|     ), | ||||
|     DirectFullReadKeyValueCacheRepo<Key, Value>(parentRepo, kvCache, locker) { | ||||
|     init { | ||||
|         if (!skipStartInvalidate) { | ||||
|             scope.launchSafelyWithoutExceptions { | ||||
|                 if (locker.writeMutex.isLocked) { | ||||
|                     initialInvalidate() | ||||
|                 } else { | ||||
|                     invalidate() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     protected open suspend fun initialInvalidate() { | ||||
|         try { | ||||
|             kvCache.actualizeAll(parentRepo, locker = null) | ||||
|         } finally { | ||||
|             locker.unlockWrite() | ||||
|         } | ||||
|     } | ||||
|     override suspend fun invalidate() { | ||||
|         kvCache.actualizeAll(parentRepo, locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun clear() { | ||||
|         parentRepo.clear() | ||||
|         kvCache.clear() | ||||
|     } | ||||
|  | ||||
|     override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) | ||||
|  | ||||
|     override suspend fun set(toSet: Map<Key, Value>) { | ||||
|         locker.withWriteLock { | ||||
|             parentRepo.set(toSet) | ||||
|             kvCache.set( | ||||
|                 toSet.filter { | ||||
|                     parentRepo.contains(it.key) | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun unset(toUnset: List<Key>) { | ||||
|         locker.withWriteLock { | ||||
|             parentRepo.unset(toUnset) | ||||
|             kvCache.unset( | ||||
|                 toUnset.filter { | ||||
|                     !parentRepo.contains(it) | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <Key, Value> KeyValueRepo<Key, Value>.directlyFullyCached( | ||||
|     kvCache: KeyValueRepo<Key, Value> = MapKeyValueRepo(), | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     skipStartInvalidate: Boolean = false, | ||||
|     locker: SmartRWLocker = SmartRWLocker() | ||||
| ) = DirectFullKeyValueCacheRepo(this, kvCache, scope, skipStartInvalidate, locker) | ||||
| @@ -0,0 +1,243 @@ | ||||
| package dev.inmo.micro_utils.repos.cache.full.direct | ||||
|  | ||||
| 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.util.ActualizeAllClearMode | ||||
| import dev.inmo.micro_utils.repos.cache.util.actualizeAll | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.* | ||||
|  | ||||
| open class DirectFullReadKeyValuesCacheRepo<Key,Value>( | ||||
|     protected open val parentRepo: ReadKeyValuesRepo<Key, Value>, | ||||
|     protected open val kvCache: KeyValueRepo<Key, List<Value>>, | ||||
|     protected val locker: SmartRWLocker = SmartRWLocker(), | ||||
| ) : ReadKeyValuesRepo<Key, Value>, DirectFullCacheRepo { | ||||
|     protected open suspend fun actualizeKey(k: Key) { | ||||
|         kvCache.actualizeAll(locker = locker, clearMode = ActualizeAllClearMode.Never) { | ||||
|             mapOf(k to parentRepo.getAll(k)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected open suspend fun actualizeAll() { | ||||
|         kvCache.actualizeAll(parentRepo, locker = locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { | ||||
|         return locker.withReadAcquire { | ||||
|             kvCache.get(k) ?.paginate( | ||||
|                 pagination.let { if (reversed) it.reverse(count(k)) else it } | ||||
|             ) ?.let { | ||||
|                 if (reversed) it.copy(results = it.results.reversed()) else it | ||||
|             } | ||||
|         } ?: emptyPaginationResult() | ||||
|     } | ||||
|  | ||||
|     override suspend fun getAll(k: Key, reversed: Boolean): List<Value> { | ||||
|         return locker.withReadAcquire { | ||||
|             kvCache.get(k) ?.optionallyReverse(reversed) | ||||
|         } ?: emptyList() | ||||
|     } | ||||
|  | ||||
|     override suspend fun getAll(reverseLists: Boolean): Map<Key, List<Value>> { | ||||
|         return locker.withReadAcquire { | ||||
|             kvCache.getAll().takeIf { it.isNotEmpty() } ?.let { | ||||
|                 if (reverseLists) { | ||||
|                     it.mapValues { it.value.reversed() } | ||||
|                 } else { | ||||
|                     it | ||||
|                 } | ||||
|             } | ||||
|         } ?: emptyMap() | ||||
|     } | ||||
|     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> { | ||||
|         return locker.withReadAcquire { | ||||
|             kvCache.keys(pagination, reversed).takeIf { it.results.isNotEmpty() } | ||||
|         } ?: emptyPaginationResult() | ||||
|     } | ||||
|  | ||||
|     override suspend fun count(): Long = locker.withReadAcquire { kvCache.count() } | ||||
|  | ||||
|     override suspend fun count(k: Key): Long = locker.withReadAcquire { kvCache.get(k) ?.size } ?.toLong() ?: 0L | ||||
|  | ||||
|     override suspend fun contains(k: Key, v: Value): Boolean = locker.withReadAcquire { kvCache.get(k) ?.contains(v) } == true | ||||
|  | ||||
|     override suspend fun contains(k: Key): Boolean = locker.withReadAcquire { kvCache.contains(k) } | ||||
|  | ||||
|     override suspend fun keys( | ||||
|         v: Value, | ||||
|         pagination: Pagination, | ||||
|         reversed: Boolean | ||||
|     ): PaginationResult<Key> { | ||||
|         val keys = locker.withReadAcquire { | ||||
|             getAllWithNextPaging { kvCache.keys(it) }.filter { kvCache.get(it)?.contains(v) == true } | ||||
|                 .optionallyReverse(reversed) | ||||
|         } | ||||
|         val result = if (keys.isNotEmpty()) { | ||||
|             keys.paginate(pagination.optionallyReverse(keys.size, reversed)).takeIf { it.results.isNotEmpty() } | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         return result ?: emptyPaginationResult() | ||||
|     } | ||||
|  | ||||
|     override suspend fun invalidate() { | ||||
|         actualizeAll() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <Key, Value> ReadKeyValuesRepo<Key, Value>.directlyCached( | ||||
|     kvCache: KeyValueRepo<Key, List<Value>>, | ||||
|     locker: SmartRWLocker = SmartRWLocker(), | ||||
| ) = DirectFullReadKeyValuesCacheRepo(this, kvCache, locker) | ||||
|  | ||||
| open class DirectFullWriteKeyValuesCacheRepo<Key,Value>( | ||||
|     parentRepo: WriteKeyValuesRepo<Key, Value>, | ||||
|     protected open val kvCache: KeyValueRepo<Key, List<Value>>, | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     protected val locker: SmartRWLocker = SmartRWLocker(), | ||||
| ) : WriteKeyValuesRepo<Key, Value> by parentRepo, DirectFullCacheRepo { | ||||
|     protected val onNewJob = parentRepo.onNewValue.onEach { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.set( | ||||
|                 it.first, | ||||
|                 kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second) | ||||
|             ) | ||||
|         } | ||||
|     }.launchIn(scope) | ||||
|     protected val onRemoveJob = parentRepo.onValueRemoved.onEach { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.set( | ||||
|                 it.first, | ||||
|                 kvCache.get(it.first)?.minus(it.second) ?: return@onEach | ||||
|             ) | ||||
|         } | ||||
|     }.launchIn(scope) | ||||
|  | ||||
|     override suspend fun invalidate() { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.clear() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <Key, Value> WriteKeyValuesRepo<Key, Value>.directlyCached( | ||||
|     kvCache: KeyValueRepo<Key, List<Value>>, | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     locker: SmartRWLocker = SmartRWLocker(), | ||||
| ) = DirectFullWriteKeyValuesCacheRepo(this, kvCache, scope, locker) | ||||
|  | ||||
| open class DirectFullKeyValuesCacheRepo<Key,Value>( | ||||
|     override val parentRepo: KeyValuesRepo<Key, Value>, | ||||
|     kvCache: KeyValueRepo<Key, List<Value>>, | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     skipStartInvalidate: Boolean = false, | ||||
|     locker: SmartRWLocker = SmartRWLocker(writeIsLocked = !skipStartInvalidate), | ||||
| ) : KeyValuesRepo<Key, Value>, | ||||
|     DirectFullReadKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, locker), | ||||
|     WriteKeyValuesRepo<Key, Value> by parentRepo { | ||||
|     init { | ||||
|         if (!skipStartInvalidate) { | ||||
|             scope.launchSafelyWithoutExceptions { | ||||
|                 if (locker.writeMutex.isLocked) { | ||||
|                     initialInvalidate() | ||||
|                 } else { | ||||
|                     invalidate() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun clearWithValue(v: Value) { | ||||
|         doAllWithCurrentPaging { | ||||
|             keys(v, it).also { | ||||
|                 remove(it.results.associateWith { listOf(v) }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected open suspend fun initialInvalidate() { | ||||
|         try { | ||||
|             kvCache.actualizeAll(parentRepo, locker = null) | ||||
|         } finally { | ||||
|             locker.unlockWrite() | ||||
|         } | ||||
|     } | ||||
|     override suspend fun invalidate() { | ||||
|         kvCache.actualizeAll(parentRepo, locker = locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun set(toSet: Map<Key, List<Value>>) { | ||||
|         locker.withWriteLock { | ||||
|             parentRepo.set(toSet) | ||||
|             kvCache.set( | ||||
|                 toSet.filter { | ||||
|                     parentRepo.contains(it.key) | ||||
|                 } | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun add(toAdd: Map<Key, List<Value>>) { | ||||
|         locker.withWriteLock { | ||||
|             parentRepo.add(toAdd) | ||||
|             toAdd.forEach { | ||||
|                 val filtered = it.value.filter { v -> | ||||
|                     parentRepo.contains(it.key, v) | ||||
|                 }.ifEmpty { | ||||
|                     return@forEach | ||||
|                 } | ||||
|                 kvCache.set( | ||||
|                     it.key, | ||||
|                     (kvCache.get(it.key) ?: emptyList()) + filtered | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun remove(toRemove: Map<Key, List<Value>>) { | ||||
|         locker.withWriteLock { | ||||
|             parentRepo.remove(toRemove) | ||||
|             toRemove.forEach { | ||||
|                 val filtered = it.value.filter { v -> | ||||
|                     !parentRepo.contains(it.key, v) | ||||
|                 }.ifEmpty { | ||||
|                     return@forEach | ||||
|                 }.toSet() | ||||
|                 val resultList = (kvCache.get(it.key) ?: emptyList()) - filtered | ||||
|                 if (resultList.isEmpty()) { | ||||
|                     kvCache.unset(it.key) | ||||
|                 } else { | ||||
|                     kvCache.set( | ||||
|                         it.key, | ||||
|                         resultList | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun clear(k: Key) { | ||||
|         locker.withWriteLock { | ||||
|             parentRepo.clear(k) | ||||
|             if (parentRepo.contains(k)) { | ||||
|                 return@withWriteLock | ||||
|             } | ||||
|             kvCache.unset(k) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <Key, Value> KeyValuesRepo<Key, Value>.directlyFullyCached( | ||||
|     kvCache: KeyValueRepo<Key, List<Value>> = MapKeyValueRepo(), | ||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||
|     skipStartInvalidate: Boolean = false, | ||||
|     locker: SmartRWLocker = SmartRWLocker(), | ||||
| ) = DirectFullKeyValuesCacheRepo(this, kvCache, scope, skipStartInvalidate, locker) | ||||
		Reference in New Issue
	
	Block a user