Compare commits

...

45 Commits

Author SHA1 Message Date
28a804d988 improve start launcher plugin logs 2023-02-22 10:28:25 +06:00
9e4bb9d678 add readme to generator and add several fixes in processor 2023-02-22 10:20:15 +06:00
9c40d7da3d add generator for koin definitions 2023-02-22 01:34:42 +06:00
2b76ad0aa9 improvements in selectByIds of CommonExposedRepo 2023-02-20 11:55:14 +06:00
e4b619e050 start 0.16.12 2023-02-20 11:54:26 +06:00
36c09feaf2 Merge pull request #222 from InsanusMokrassar/0.16.11
0.16.11
2023-02-20 00:49:12 +06:00
2d68321503 Update build.gradle 2023-02-20 00:19:27 +06:00
85455ab21c improvements in language codes 2023-02-19 19:34:42 +06:00
18d63eb980 start 0.16.11 2023-02-19 19:20:03 +06:00
2e429e9704 Merge pull request #221 from InsanusMokrassar/0.16.10
0.16.10
2023-02-17 13:54:09 +06:00
f4af28059b improvements in ReadKeyValueFromCRUDRepo 2023-02-17 13:45:22 +06:00
c1476bd075 add ReadCRUDFromKeyValueRepo 2023-02-15 20:16:54 +06:00
16c720fddd start 0.16.10 2023-02-15 20:14:55 +06:00
8b4b4a5eca Merge pull request #220 from InsanusMokrassar/0.16.9
0.16.9
2023-02-13 16:09:55 +06:00
32e6e5b7e2 Update CHANGELOG.md 2023-02-13 16:09:44 +06:00
a9f7fd8e32 new extension HttpResponse.bodyOrNullOnNoContent 2023-02-10 15:17:08 +06:00
95be1a26f2 improvements in HttpResponse.bodyOrNull 2023-02-10 15:14:03 +06:00
ef9b31aee0 start 0.16.9 2023-02-05 18:17:20 +06:00
df3c01ff0a Merge pull request #219 from InsanusMokrassar/0.16.8
0.16.8
2023-02-04 01:46:43 +06:00
4704c5a33d Update CHANGELOG.md 2023-02-04 01:39:49 +06:00
225c06550a fixes in FileKeyValueRepo 2023-02-03 10:23:52 +06:00
f0987614c6 fixes in FileKeyValueRepo 2023-02-03 10:07:24 +06:00
269c2876f3 actualize all make it possible to disable clear on actualization 2023-02-02 20:07:31 +06:00
168d6acf7c actualizeAll now is overridable in auto recache repos 2023-02-02 20:05:25 +06:00
a5f718e257 add meppers for kv<->kvs repos 2023-02-02 12:29:00 +06:00
4f68459582 start add kv to kvs transformations 2023-02-02 10:15:37 +06:00
442db122cf remove redundant id getter in keyvalues autocached repo 2023-02-01 23:02:06 +06:00
580d757be2 fixes in download file and upgrade ktor version 2023-02-01 22:28:17 +06:00
47b0f6d2d8 fixes 2023-02-01 13:03:51 +06:00
3f6f6ebc2b realize invalidate method in fallback cache repos 2023-01-31 10:09:55 +06:00
2645ea29d6 Update ActionWrapper.kt 2023-01-31 00:11:45 +06:00
79f2041565 Update AutoRecacheReadCRUDRepo.kt 2023-01-30 23:59:48 +06:00
4a7567f288 Update AutoRecacheWriteCRUDRepo.kt 2023-01-30 23:59:19 +06:00
8a890ed6ed Update AutoRecacheReadKeyValueRepo.kt 2023-01-30 23:58:43 +06:00
3d90df6897 Update AutoRecacheWriteKeyValueRepo.kt 2023-01-30 23:58:11 +06:00
681c13144a Update AutoRecacheReadKeyValuesRepo.kt 2023-01-30 23:57:40 +06:00
b64f2e6d32 Update AutoRecacheWriteKeyValuesRepo.kt 2023-01-30 23:56:57 +06:00
428eabb1bd Create FallbackCacheRepo.kt 2023-01-30 23:54:32 +06:00
2162e83bce fixes in actualizeAll 2023-01-29 22:51:39 +06:00
6142022283 improve actualizeAll to lazily clear repo 2023-01-29 22:46:32 +06:00
e6d9c8250f add all autorecaches repos 2023-01-29 22:18:22 +06:00
46178e723b fixes 2023-01-29 20:54:32 +06:00
605f55acd2 fixes in Write* cache repos 2023-01-29 20:35:10 +06:00
0f8b69aa60 start 0.16.8 2023-01-29 20:35:10 +06:00
551d8ec480 Merge pull request #218 from InsanusMokrassar/0.16.7
0.16.7
2023-01-29 13:20:16 +06:00
58 changed files with 1849 additions and 109 deletions

View File

@@ -1,5 +1,58 @@
# Changelog
## 0.16.12
* `Repos`:
* `Exposed`:
* `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach
* `Koin`:
* `Generator`:
* Module has been created
## 0.16.11
* `LanguageCodes`:
* In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to
`IetfLanguageCode`
## 0.16.10
* `Repos`:
* `Cache`:
* New transformer type: `ReadCRUDFromKeyValueRepo`
* New transformer type: `ReadKeyValueFromCRUDRepo`
* `Pagination`:
* New `paginate` extensions with `reversed` support for `List`/`Set`
## 0.16.9
* `Versions`:
* `Koin`: `3.2.2` -> `3.3.2`
* `AppCompat`: `1.5.1` -> `1.6.0`
* `Ktor`:
* `Client`
* `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null
* New extension `HttpResponse.bodyOrNullOnNoContent`
## 0.16.8
* `Versions`:
* `Ktor`: `2.2.2` -> `2.2.3`
* `Ktor`:
* `Client`
* Fixes in `HttpClient.uniUpload`
* `Server`
* Fixes in `PartData.FileItem.download`
* `Repos`:
* `Cache`:
* New type of caches: `FallbackCacheRepo`
* Fixes in `Write*` variants of cached repos
* New type `ActionWrapper`
* New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
* `Common`:
* New transformations for key-value and key-values vice-verse
* Fixes in `FileReadKeyValueRepo`
## 0.16.7
* `Common`:

View File

@@ -9,6 +9,7 @@ buildscript {
dependencies {
classpath libs.buildscript.kt.gradle
classpath libs.buildscript.kt.serialization
classpath libs.buildscript.kt.ksp
classpath libs.buildscript.jb.dokka
classpath libs.buildscript.gh.release
classpath libs.buildscript.android.gradle

View File

@@ -29,6 +29,6 @@ allprojects {
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
}
}

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.16.7
android_code_version=175
version=0.16.12
android_code_version=180

View File

@@ -13,25 +13,28 @@ jb-dokka = "1.7.20"
klock = "3.4.0"
uuid = "0.6.0"
ktor = "2.2.2"
ktor = "2.2.3"
gh-release = "2.4.1"
koin = "3.2.2"
koin = "3.3.2"
ksp = "1.7.20-1.0.8"
kotlin-poet = "1.12.0"
android-gradle = "7.3.0"
dexcount = "3.1.0"
android-coreKtx = "1.9.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.5.1"
android-appCompat = "1.6.0"
android-fragment = "1.5.5"
android-espresso = "3.4.0"
android-test = "1.1.3"
android-props-minSdk = "21"
android-props-compileSdk = "33"
android-props-buildTools = "33.0.0"
android-props-buildTools = "33.0.1"
[libraries]
@@ -83,9 +86,16 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
# ksp dependencies
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
# Buildscript
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

118
jvm.publish.gradle Normal file
View File

@@ -0,0 +1,118 @@
apply plugin: 'maven-publish'
task javadocJar(type: Jar) {
from javadoc
classifier = 'javadoc'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
publishing {
publications {
maven(MavenPublication) {
from components.java
artifact javadocJar
artifact sourcesJar
pom {
resolveStrategy = Closure.DELEGATE_FIRST
description = "It is set of projects with micro tools for avoiding of routines coding"
name = "${project.name}"
url = "https://github.com/InsanusMokrassar/MicroUtils/"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
url = "https://github.com/InsanusMokrassar/MicroUtils.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
}
}
}
repositories {
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
}
}
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
maven {
name = "Gitea"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}
}
}
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
}

1
jvm.publish.kpsb Normal file
View File

@@ -0,0 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

100
koin/generator/README.md Normal file
View File

@@ -0,0 +1,100 @@
# Koin generator
It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`.
1. [What may do this generator](#what-may-do-this-generator)
2. [How to add generator](#how-to-add-generator)
## What may do this generator
Let's imagine you want to have shortcuts in koin, to get something easily:
```kotlin
val koin: Koin// some initialization
val someUrl = koin.serverUrl
```
So, in that case you may mark containing file with next annotation (in the beginning of file):
```kotlin
@file:GenerateKoinDefinition("serverUrl", String::class, nullable = false)
```
If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content:
```kotlin
public val Scope.serverUrl: String
get() = get(named("serverUrl"))
public val Koin.serverUrl: String
get() = get(named("serverUrl"))
public fun Module.serverUrlSingle(createdAtStart: Boolean = false,
definition: Definition<String>): KoinDefinition<String> =
single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition)
public fun Module.serverUrlFactory(definition: Definition<String>):
KoinDefinition<String> = factory(named("serverUrl"), definition = definition)
```
Besides, you may use the generics:
```kotlin
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false)
```
Will generate:
```kotlin
public val Scope.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public val Koin.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
```
In case you wish not to generate single:
```kotlin
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false)
```
And you will take next code:
```kotlin
public val Scope.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public val Koin.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
```
## How to add generator
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
**Note: $microutils_version in the version of MicroUtils library in your project**
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`:
```groovy
dependencies {
add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module
add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module
}
ksp { // this generator do not require any arguments and we should left `ksp` empty
}
```

View File

@@ -0,0 +1,15 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}
apply from: "$publishJvmOnlyPath"
repositories {
mavenCentral()
}
dependencies {
api project(":micro_utils.koin")
api libs.kotlin.poet
api libs.ksp
}

View File

@@ -0,0 +1,178 @@
package dev.inmo.micro_utils.koin.generator
import com.google.devtools.ksp.KSTypeNotPresentException
import com.google.devtools.ksp.KSTypesNotPresentException
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
import org.koin.core.module.Module
import org.koin.core.scope.Scope
import java.io.File
import kotlin.reflect.KClass
class Processor(
private val codeGenerator: CodeGenerator
) : SymbolProcessor {
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation(
GenerateKoinDefinition::class.qualifiedName!!
).filterIsInstance<KSFile>().forEach { ksFile ->
FileSpec.builder(
ksFile.packageName.asString(),
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
).apply {
addFileComment(
"""
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
TO REGENERATE IT JUST DELETE FILE
ORIGINAL FILE: ${ksFile.fileName}
""".trimIndent()
)
ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
val type = runCatching {
it.type.asTypeName()
}.getOrElse { e ->
if (e is KSTypeNotPresentException) {
e.ksType.toClassName()
} else {
throw e
}
}
val targetType = runCatching {
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
}.getOrElse { e ->
when (e) {
is KSTypeNotPresentException -> e.ksType.toClassName()
}
if (e is KSTypesNotPresentException) {
type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray())
} else {
throw e
}
}.copy(
nullable = it.nullable
)
fun addGetterProperty(
receiver: KClass<*>
) {
addProperty(
PropertySpec.builder(
it.name,
targetType,
).apply {
addKdoc(
"""
@return Definition by key "${it.name}"
""".trimIndent()
)
getter(
FunSpec.getterBuilder().apply {
addCode(
"return " + (if (it.nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${it.name}\"))"
)
}.build()
)
receiver(receiver)
}.build()
)
}
addGetterProperty(Scope::class)
addGetterProperty(Koin::class)
if (it.generateSingle) {
addFunction(
FunSpec.builder("${it.name}Single").apply {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"createdAtStart",
Boolean::class
).apply {
defaultValue("false")
}.build()
)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
)
}.build()
)
}
if (it.generateFactory) {
addFunction(
FunSpec.builder("${it.name}Factory").apply {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return factory(named(\"${it.name}\"), definition = definition)"
)
}.build()
)
}
addImport("org.koin.core.qualifier", "named")
}
}.build().let {
File(
File(ksFile.filePath).parent,
"GeneratedDefinitions${ksFile.fileName}"
).takeIf { !it.exists() } ?.apply {
parentFile.mkdirs()
writer().use { writer ->
it.writeTo(writer)
}
}
}
}
return emptyList()
}
}

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.koin.generator
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
environment.codeGenerator
)
}

View File

@@ -0,0 +1 @@
dev.inmo.micro_utils.koin.generator.Provider

View File

@@ -0,0 +1,27 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "com.google.devtools.ksp"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.koin")
}
}
}
}
dependencies {
add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
}
ksp {
}

View File

@@ -0,0 +1,38 @@
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
// TO REGENERATE IT JUST DELETE FILE
// ORIGINAL FILE: Test.kt
package dev.inmo.micro_utils.koin.generator.test
import kotlin.Boolean
import kotlin.String
import org.koin.core.Koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
/**
* @return Definition by key "sampleInfo"
*/
public val Scope.sampleInfo: Test<String>
get() = get(named("sampleInfo"))
/**
* @return Definition by key "sampleInfo"
*/
public val Koin.sampleInfo: Test<String>
get() = get(named("sampleInfo"))
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)

View File

@@ -0,0 +1,13 @@
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
package dev.inmo.micro_utils.koin.generator.test
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
class Test<T>(
koin: Koin
) {
init {
koin.sampleInfo
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.koin.generator.test"/>

View File

@@ -0,0 +1,28 @@
package dev.inmo.micro_utils.koin.annotations
import kotlin.reflect.KClass
/**
* Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
* and [org.koin.core.Koin]
*
* @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
* @param type Type of extensions. It is base star-typed class
* @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
* G2::class`, the result type will be `Something<G1, G2>`
* @param nullable In case when true, extension will not throw error when definition has not been registered in koin
* @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
* the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
* @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
* the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
*/
@Target(AnnotationTarget.FILE)
@Repeatable
annotation class GenerateKoinDefinition(
val name: String,
val type: KClass<*>,
vararg val typeArgs: KClass<*>,
val nullable: Boolean = true,
val generateSingle: Boolean = true,
val generateFactory: Boolean = true
)

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
@@ -13,7 +14,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: Qualifier? = null,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
): KoinDefinition<*> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
@@ -21,7 +22,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: String,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
): KoinDefinition<*> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
@@ -8,6 +9,6 @@ import kotlin.reflect.KClass
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
): KoinDefinition<*> {
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
}

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
@@ -14,7 +15,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
): KoinDefinition<*> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
@@ -24,7 +25,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
): KoinDefinition<*> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
@@ -9,6 +10,6 @@ inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
): KoinDefinition<*> {
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
}

View File

@@ -4,6 +4,10 @@ import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
status == HttpStatusCode.OK
} ?.body<T>()
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
) = takeIf(statusFilter) ?.body<T>()
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
it.status != HttpStatusCode.NoContent
}

View File

@@ -17,8 +17,11 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
import java.io.File
/**
@@ -29,6 +32,7 @@ import java.io.File
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
@OptIn(InternalSerializationApi::class)
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
@@ -60,7 +64,7 @@ actual suspend fun <T> HttpClient.uniUpload(
)
else -> append(
k,
stringFormat.encodeToString(v)
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
)
}
}

View File

@@ -10,8 +10,8 @@ import java.io.File
fun PartData.FileItem.download(target: File) {
provider().use { input ->
target.outputStream().use {
input.copyTo(it.asOutput())
target.outputStream().asOutput().use {
input.copyTo(it)
}
}
}

View File

@@ -5,3 +5,11 @@ plugins {
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
androidMain {
dependsOn jvmMain
}
}
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.language_codes
import java.util.Locale
fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code)
fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault()
fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag())

View File

@@ -0,0 +1,29 @@
package dev.inmo.micro_utils.pagination.utils
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
else -> if (reverse) {
reversed()
} else {
this
}
}
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
}
} else {
this
}

View File

@@ -32,6 +32,24 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
)
}
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse(
size,
reversed
)
val firstIndex = maxOf(actualPagination.firstIndex, 0)
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
}
return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult(
with,
size.toLong()
)
}
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
return this.drop(with.firstIndex).take(with.size).createPaginationResult(
with,
@@ -39,30 +57,20 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
)
}
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
else -> if (reverse) {
reversed()
} else {
this
}
}
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse(
size,
reversed
)
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
val firstIndex = maxOf(actualPagination.firstIndex, 0)
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
}
} else {
this
return this.drop(firstIndex).take(lastIndex - firstIndex).optionallyReverse(reversed).createPaginationResult(
with,
size.toLong()
)
}

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.repos.cache
/**
* Any inheritor of this should work with next logic: try to take data from their original repo, if successful - save data to internal
* [dev.inmo.micro_utils.repos.cache.cache.FullKVCache] or try to take data from that internal cache
*/
interface FallbackCacheRepo : CacheRepo

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.repos.cache.fallback
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import kotlinx.coroutines.withTimeout
/**
* Realizations should [wrap] the work with some conditions like retries on exceptions, calling timeout, etc.
*
* @see Timeouted
* @see Direct
*/
interface ActionWrapper {
/**
* Should execute [block] to take the result [T], but may return failure in case when something went wrong.
* This method should never throw any [Exception]
*/
suspend fun <T> wrap(block: suspend () -> T): Result<T>
/**
* This type of [ActionWrapper]s will use [withTimeout]([timeoutMillis]) and if original call
* will not return anything in that timeout just return [Result] with failure
*/
class Timeouted(private val timeoutMillis: Long) : ActionWrapper {
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely {
withTimeout(timeoutMillis) {
block()
}
}
}
/**
* It is passthrough variant of [ActionWrapper] which will just call incoming block with wrapping into [runCatchingSafely]
*/
object Direct : ActionWrapper {
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely { block() }
}
}

View File

@@ -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<RegisteredObject, Id, InputObject>(
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
scope: CoroutineScope,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
actionWrapper: ActionWrapper = ActionWrapper.Direct,
idGetter: (RegisteredObject) -> Id
) : AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
originalRepo,
scope,
kvCache,
recacheDelay,
actionWrapper,
idGetter
),
WriteCRUDRepo<RegisteredObject, Id, InputObject> by AutoRecacheWriteCRUDRepo(originalRepo, scope, kvCache, idGetter),
CRUDRepo<RegisteredObject, Id, InputObject> {
constructor(
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
}

View File

@@ -0,0 +1,86 @@
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.cache.FallbackCacheRepo
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<RegisteredObject, Id>(
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
protected val idGetter: (RegisteredObject) -> Id
) : ReadCRUDRepo<RegisteredObject, Id>, FallbackCacheRepo {
val autoUpdateJob = scope.launch {
while (isActive) {
actualizeAll()
delay(recacheDelay)
}
}
constructor(
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
protected open suspend fun actualizeAll(): Result<Unit> {
return runCatchingSafely {
kvCache.actualizeAll(originalRepo)
}
}
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<RegisteredObject> = 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<Id> = 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)
override suspend fun invalidate() {
actualizeAll()
}
}

View File

@@ -0,0 +1,65 @@
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.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<RegisteredObject, Id, InputObject>(
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
protected val idGetter: (RegisteredObject) -> Id
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
override val deletedObjectsIdsFlow: Flow<Id>
get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged()
override val newObjectsFlow: Flow<RegisteredObject>
get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged()
override val updatedObjectsFlow: Flow<RegisteredObject>
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<UpdatedValuePair<Id, InputObject>>
): List<RegisteredObject> = 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<Id>) = originalRepo.deleteById(ids).also {
kvCache.unset(ids)
}
override suspend fun create(values: List<InputObject>): List<RegisteredObject> = originalRepo.create(values).onEach {
kvCache.set(idGetter(it), it)
}
override suspend fun invalidate() {
kvCache.clear()
}
}

View File

@@ -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<Id, RegisteredObject>(
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
scope: CoroutineScope,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
actionWrapper: ActionWrapper = ActionWrapper.Direct,
idGetter: (RegisteredObject) -> Id
) : AutoRecacheReadKeyValueRepo<Id, RegisteredObject> (
originalRepo,
scope,
kvCache,
recacheDelay,
actionWrapper,
idGetter
),
WriteKeyValueRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValueRepo(originalRepo, scope, kvCache),
KeyValueRepo<Id, RegisteredObject> {
constructor(
originalRepo: KeyValueRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
toUnset
).also {
kvCache.unsetWithValues(toUnset)
}
}

View File

@@ -0,0 +1,96 @@
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.cache.FallbackCacheRepo
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<Id, RegisteredObject>(
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
protected val idGetter: (RegisteredObject) -> Id
) : ReadKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
val autoUpdateJob = scope.launch {
while (isActive) {
actualizeAll()
delay(recacheDelay)
}
}
constructor(
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
protected open suspend fun actualizeAll(): Result<Unit> {
return runCatchingSafely {
kvCache.actualizeAll(originalRepo)
}
}
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(k, it)
} ?: kvCache.get(k)
override suspend fun values(
pagination: Pagination,
reversed: Boolean
): PaginationResult<RegisteredObject> = 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<Id> = actionWrapper.wrap {
originalRepo.keys(pagination, reversed)
}.getOrElse { kvCache.keys(pagination, reversed) }
override suspend fun keys(
v: RegisteredObject,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Id> = actionWrapper.wrap {
originalRepo.keys(v, pagination, reversed)
}.getOrElse { kvCache.keys(v, pagination, reversed) }
override suspend fun invalidate() {
actualizeAll()
}
}

View File

@@ -0,0 +1,54 @@
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.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
open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
protected val originalRepo: WriteKeyValueRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
override val onValueRemoved: Flow<Id>
get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged()
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
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<RegisteredObject>) = originalRepo.unsetWithValues(
toUnset
).also {
kvCache.unsetWithValues(toUnset)
}
override suspend fun unset(toUnset: List<Id>) = originalRepo.unset(
toUnset
).also {
kvCache.unset(toUnset)
}
override suspend fun set(toSet: Map<Id, RegisteredObject>) = originalRepo.set(
toSet
).also {
kvCache.set(toSet)
}
override suspend fun invalidate() {
kvCache.clear()
}
}

View File

@@ -0,0 +1,37 @@
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<Id, RegisteredObject>(
override val originalRepo: KeyValuesRepo<Id, RegisteredObject>,
scope: CoroutineScope,
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
actionWrapper: ActionWrapper = ActionWrapper.Direct
) : AutoRecacheReadKeyValuesRepo<Id, RegisteredObject> (
originalRepo,
scope,
kvCache,
recacheDelay,
actionWrapper
),
WriteKeyValuesRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValuesRepo(originalRepo, scope, kvCache),
KeyValuesRepo<Id, RegisteredObject> {
constructor(
originalRepo: KeyValuesRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
override suspend fun clearWithValue(v: RegisteredObject) {
super.clearWithValue(v)
}
}

View File

@@ -0,0 +1,145 @@
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.cache.FallbackCacheRepo
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<Id, RegisteredObject>(
protected open val originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct
) : ReadKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
val autoUpdateJob = scope.launch {
while (isActive) {
actualizeAll()
delay(recacheDelay)
}
}
constructor(
originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
protected open suspend fun actualizeAll(): Result<Unit> {
return runCatchingSafely {
kvCache.actualizeAll(originalRepo)
}
}
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<Id> = actionWrapper.wrap {
originalRepo.keys(v, pagination, reversed)
}.getOrElse {
val results = mutableListOf<Id>()
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<Id> = actionWrapper.wrap {
originalRepo.keys(pagination, reversed)
}.getOrElse { kvCache.keys(pagination, reversed) }
override suspend fun get(
k: Id,
pagination: Pagination,
reversed: Boolean
): PaginationResult<RegisteredObject> = 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)
}
override suspend fun invalidate() {
actualizeAll()
}
}

View File

@@ -0,0 +1,86 @@
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.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
open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
protected val originalRepo: WriteKeyValuesRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache()
) : WriteKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
override val onValueRemoved: Flow<Pair<Id, RegisteredObject>>
get() = originalRepo.onValueRemoved
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
get() = originalRepo.onNewValue
override val onDataCleared: Flow<Id>
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<Id, List<RegisteredObject>>) {
originalRepo.remove(toRemove)
toRemove.forEach { (k, v) ->
kvCache.set(k, (kvCache.get(k) ?: return@forEach) - v)
}
}
override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) {
originalRepo.add(toAdd)
toAdd.forEach { (k, v) ->
kvCache.set(k, (kvCache.get(k) ?: return@forEach) + v)
}
}
override suspend fun invalidate() {
kvCache.clear()
}
}

View File

@@ -3,11 +3,9 @@ package dev.inmo.micro_utils.repos.cache.full
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

View File

@@ -9,6 +9,7 @@ import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
open class FullReadKeyValueCacheRepo<Key,Value>(
@@ -80,7 +81,7 @@ fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
) = FullReadKeyValueCacheRepo(this, kvCache)
open class FullWriteKeyValueCacheRepo<Key,Value>(
protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
parentRepo: WriteKeyValueRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
@@ -98,7 +99,7 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
open class FullKeyValueCacheRepo<Key,Value>(
override val parentRepo: KeyValueRepo<Key, Value>,
protected open val parentRepo: KeyValueRepo<Key, Value>,
kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),

View File

@@ -112,7 +112,7 @@ fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
) = FullReadKeyValuesCacheRepo(this, kvCache)
open class FullWriteKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
parentRepo: WriteKeyValuesRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
@@ -140,7 +140,7 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
open class FullKeyValuesCacheRepo<Key,Value>(
override val parentRepo: KeyValuesRepo<Key, Value>,
protected open val parentRepo: KeyValuesRepo<Key, Value>,
kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),

View File

@@ -2,60 +2,52 @@ 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
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
clear: Boolean = true,
getAll: () -> Map<K, V>
) {
clear()
set(getAll())
set(
getAll().also {
if (clear) {
clear()
}
}
)
}
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
repo: ReadKeyValueRepo<K, V>
repo: ReadKeyValueRepo<K, V>,
clear: Boolean = true,
) {
clear()
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
val initPagination = FirstPagePagination(count)
doForAllWithNextPaging(initPagination) {
keys(it).also {
set(
it.results.mapNotNull { k -> repo.get(k) ?.let { k to it } }
)
}
actualizeAll(clear) {
repo.getAll { keys(it) }.toMap()
}
}
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
repo: ReadKeyValuesRepo<K, V>
repo: ReadKeyValuesRepo<K, V>,
clear: Boolean = true,
) {
clear()
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
val initPagination = FirstPagePagination(count)
doForAllWithNextPaging(initPagination) {
keys(it).also {
set(
it.results.associateWith { k -> repo.getAll(k) }
)
}
actualizeAll(clear) {
repo.getAll { keys(it) }.toMap()
}
}
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
repo: ReadCRUDRepo<V, K>
repo: ReadCRUDRepo<V, K>,
clear: Boolean = true,
) {
clear()
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
val initPagination = FirstPagePagination(count)
doForAllWithNextPaging(initPagination) {
keys(it).also {
set(
it.results.mapNotNull { k -> repo.getById(k) ?.let { k to it } }
)
}
actualizeAll(clear) {
repo.getAllByWithNextPaging {
getIdsByPagination(it)
}.mapNotNull { it to (repo.getById(it) ?: return@mapNotNull null) }.toMap()
}
}

View File

@@ -2,6 +2,8 @@ package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.paginate
import kotlinx.coroutines.flow.Flow
/**
@@ -17,24 +19,32 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key): Value?
/**
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
* ascending sort for [Key]s
*/
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
/**
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
* ascending sort for [Key]s
*/
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
/**
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
* ascending sort for [Key]s
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
* ascending sort for [Key]s.
*
* **DEFAULT REALIZATION IS NOT OPTIMAL AND HAS BEEN ADDED TO COVER CASES OF DIFFERENT COMMON MAPPINGS AND TRANSFORMATIONS**
*
* @param v This value should be used to exclude from search the items with different [Value]s
*/
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> {
return getAllWithNextPaging {
keys(it)
}.filter {
get(it) == v
}.paginate(pagination, reversed)
}
/**
* @return true if [key] is presented in current collection or false otherwise

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.repos.transforms.crud
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import kotlin.js.JsName
import kotlin.jvm.JvmName
fun <K, V> ReadKeyValueRepo<K, V>.asReadCRUDRepo() = ReadCRUDFromKeyValueRepo(this)

View File

@@ -0,0 +1,20 @@
package dev.inmo.micro_utils.repos.transforms.crud
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.ReadKeyValueRepo
open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>(
protected open val original: ReadKeyValueRepo<IdType, RegisteredType>
) : ReadCRUDRepo<RegisteredType, IdType> {
override suspend fun contains(id: IdType): Boolean = original.contains(id)
override suspend fun count(): Long = original.count()
override suspend fun getByPagination(pagination: Pagination): PaginationResult<RegisteredType> = original.values(pagination)
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = original.keys(pagination)
override suspend fun getById(id: IdType): RegisteredType? = original.get(id)
}

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.repos.transforms.kv
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import kotlin.js.JsName
import kotlin.jvm.JvmName
fun <K, V> ReadKeyValuesRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromKeyValuesRepo(this)
fun <K, V> KeyValuesRepo<K, V>.asKeyValueRepo() = KeyValueFromKeyValuesRepo(this)
fun <K, V> ReadCRUDRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromCRUDRepo(this)

View File

@@ -0,0 +1,41 @@
package dev.inmo.micro_utils.repos.transforms.kv
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.changeResults
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
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.KeyValuesRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
open class KeyValueFromKeyValuesRepo<Key, Value>(
private val original: KeyValuesRepo<Key, Value>
) : KeyValueRepo<Key, List<Value>>, ReadKeyValueFromKeyValuesRepo<Key, Value>(original) {
override val onNewValue: Flow<Pair<Key, List<Value>>> = merge(
original.onNewValue,
original.onValueRemoved
).mapNotNull {
it.first to (get(it.first) ?: return@mapNotNull null)
}
override val onValueRemoved: Flow<Key> = original.onDataCleared
override suspend fun unset(toUnset: List<Key>) {
toUnset.forEach {
original.clear(it)
}
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
original.set(toSet)
}
}

View File

@@ -0,0 +1,46 @@
package dev.inmo.micro_utils.repos.transforms.kv
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.changeResults
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
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.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
import kotlin.jvm.JvmInline
@JvmInline
value class ReadKeyValueFromCRUDRepo<Key, Value>(
private val original: ReadCRUDRepo<Value, Key>
) : ReadKeyValueRepo<Key, Value> {
override suspend fun get(k: Key): Value? = original.getById(k)
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = original.getByPagination(
pagination.optionallyReverse(count(), reversed)
).let {
if (reversed) {
it.changeResultsUnchecked(it.results.reversed())
} else {
it
}
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = original.getIdsByPagination(
pagination.optionallyReverse(count(), reversed)
).let {
if (reversed) {
it.changeResultsUnchecked(it.results.reversed())
} else {
it
}
}
override suspend fun count(): Long = original.count()
override suspend fun contains(key: Key): Boolean = original.contains(key)
}

View File

@@ -0,0 +1,67 @@
package dev.inmo.micro_utils.repos.transforms.kv
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.changeResults
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
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.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
open class ReadKeyValueFromKeyValuesRepo<Key, Value>(
private val original: ReadKeyValuesRepo<Key, Value>
) : ReadKeyValueRepo<Key, List<Value>> {
override suspend fun get(k: Key): List<Value>? = original.getAll(k)
override suspend fun values(
pagination: Pagination,
reversed: Boolean
): PaginationResult<List<Value>> {
val keys = keys(pagination, reversed)
return keys.changeResults(
keys.results.mapNotNull {
get(it)
}
)
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
return original.keys(pagination, reversed)
}
override suspend fun count(): Long {
return original.count()
}
override suspend fun contains(key: Key): Boolean {
return original.contains(key)
}
override suspend fun keys(v: List<Value>, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
val keys = mutableSetOf<Key>()
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
original.keys(it).also {
it.results.forEach {
val values = get(it) ?: return@forEach
if (values.containsAll(v) && v.containsAll(values)) {
keys.add(it)
}
}
}
}
val paginated = keys.paginate(
pagination.optionallyReverse(keys.count(), reversed)
)
return if (reversed) {
paginated.changeResultsUnchecked(paginated.results.reversed())
} else {
paginated
}
}
}

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.repos.transforms.kvs
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import kotlin.js.JsName
import kotlin.jvm.JvmName
fun <K, V, VI : Iterable<V>> ReadKeyValueRepo<K, VI>.asReadKeyValuesRepo() = ReadKeyValuesFromKeyValueRepo(this)
fun <K, V, VI : Iterable<V>> KeyValueRepo<K, VI>.asKeyValuesRepo(
listToValuesIterable: suspend (List<V>) -> VI
): KeyValuesFromKeyValueRepo<K, V, VI> = KeyValuesFromKeyValueRepo(this, listToValuesIterable)
@JvmName("asListKeyValuesRepo")
@JsName("asListKeyValuesRepo")
fun <K, V> KeyValueRepo<K, List<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, List<V>> = asKeyValuesRepo { it }
@JvmName("asSetKeyValuesRepo")
@JsName("asSetKeyValuesRepo")
fun <K, V> KeyValueRepo<K, Set<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, Set<V>> = asKeyValuesRepo { it.toSet() }

View File

@@ -0,0 +1,77 @@
package dev.inmo.micro_utils.repos.transforms.kvs
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.unset
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlin.js.JsName
import kotlin.jvm.JvmName
open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
private val original: KeyValueRepo<Key, ValuesIterable>,
private val listToValuesIterable: suspend (List<Value>) -> ValuesIterable
) : KeyValuesRepo<Key, Value>, ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable>(original) {
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
private val _onValueRemoved = MutableSharedFlow<Pair<Key, Value>>()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
override val onDataCleared: Flow<Key> = original.onValueRemoved
override suspend fun clearWithValue(v: Value) {
val keys = mutableSetOf<Key>()
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
original.keys(it).also {
it.results.forEach {
if (contains(it, v)) {
keys.add(it)
}
}
}
}
original.unset(keys.toList())
}
override suspend fun clear(k: Key) {
original.unset(k)
}
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
original.set(
toRemove.mapNotNull { (k, removing) ->
val exists = original.get(k) ?: return@mapNotNull null
k to listToValuesIterable(exists - removing).also {
if (it.firstOrNull() == null) {
original.unset(k)
return@mapNotNull null
}
}
}.toMap()
)
toRemove.forEach { (k, v) ->
v.forEach {
_onValueRemoved.emit(k to it)
}
}
}
override suspend fun add(toAdd: Map<Key, List<Value>>) {
original.set(
toAdd.mapNotNull { (k, adding) ->
val exists = original.get(k) ?: emptyList()
k to listToValuesIterable(exists + adding)
}.toMap()
)
toAdd.forEach { (k, v) ->
v.forEach {
_onNewValue.emit(k to it)
}
}
}
}

View File

@@ -0,0 +1,72 @@
package dev.inmo.micro_utils.repos.transforms.kvs
import dev.inmo.micro_utils.pagination.FirstPagePagination
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.emptyPaginationResult
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.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
open class ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
private val original: ReadKeyValueRepo<Key, ValuesIterable>
) : ReadKeyValuesRepo<Key, Value> {
override suspend fun get(
k: Key,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Value> {
val iterable = original.get(k) ?: return emptyPaginationResult(pagination)
val paginated = iterable.paginate(
pagination.optionallyReverse(iterable.count(), reversed)
)
return if (reversed) {
paginated.changeResultsUnchecked(paginated.results.reversed())
} else {
paginated
}
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = original.keys(pagination, reversed)
override suspend fun count(): Long = original.count()
override suspend fun count(k: Key): Long = original.get(k) ?.count() ?.toLong() ?: 0L
override suspend fun contains(k: Key, v: Value): Boolean = original.get(k) ?.contains(v) == true
override suspend fun contains(k: Key): Boolean = original.contains(k)
override suspend fun keys(
v: Value,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> {
val keys = mutableSetOf<Key>()
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
original.keys(it).also {
it.results.forEach {
if (contains(it, v)) {
keys.add(it)
}
}
}
}
val paginated = keys.paginate(
pagination.optionallyReverse(keys.count(), reversed)
)
return if (reversed) {
paginated.changeResultsUnchecked(paginated.results.reversed())
} else {
paginated
}
}
}

View File

@@ -31,11 +31,19 @@ class FileReadKeyValueRepo(
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
val filesList = folder.list()
val files: Array<String> = if (resultPagination.firstIndex < count) {
val filesPaths = filesList.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive.coerceAtMost(filesList.size))
if (reversed) {
filesPaths.reversedArray()
} else {
filesPaths
}
} else {
emptyArray<String>()
}
return filesPaths.map { File(folder, it) }.createPaginationResult(
return files.map { File(folder, it) }.createPaginationResult(
resultPagination,
count
)
@@ -44,11 +52,21 @@ class FileReadKeyValueRepo(
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
val filesList = folder.list()
val files: Array<String> = if (resultPagination.firstIndex < count) {
val filesPaths = filesList.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive.coerceAtMost(filesList.size))
if (reversed) {
filesPaths.reversedArray()
} else {
filesPaths
}
} else {
emptyArray<String>()
}
return filesPaths.toList().createPaginationResult(
return files.toList().createPaginationResult(
resultPagination,
count
)

View File

@@ -7,15 +7,9 @@ interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
val ResultRow.asId: IdType
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
get() = { list ->
if (list.isEmpty()) {
Op.FALSE
} else {
var op = selectById(list.first())
(1 until list.size).forEach {
op = op.or(selectById(list[it]))
}
op
}
get() = {
it.foldRight<IdType, Op<Boolean>?>(null) { id, acc ->
acc ?.or(selectById(id)) ?: selectById(id)
} ?: Op.FALSE
}
}

View File

@@ -7,6 +7,8 @@ String[] includes = [
":safe_wrapper",
":crypto",
":koin",
":koin:generator",
":koin:generator:test",
":selector:common",
":pagination:common",
":pagination:exposed",

View File

@@ -39,14 +39,18 @@ object StartLauncherPlugin : StartPlugin {
includes(
config.plugins.mapNotNull {
val pluginName = it::class.simpleName ?: it.toString()
runCatching {
logger.i { "Start koin module registration for $pluginName" }
module {
with(it) {
setupDI(rawJsonObject)
}
}
}.onFailure { e ->
logger.w("Unable to load DI part of $it", e)
logger.w("Unable to register koin module of $pluginName", e)
}.onSuccess {
logger.i("Successfully registered koin module of $pluginName")
}.getOrNull()
}
)
@@ -76,16 +80,17 @@ object StartLauncherPlugin : StartPlugin {
logger.i("Start starting of subplugins")
val scope = koin.get<CoroutineScope>()
koin.get<Config>().plugins.map { plugin ->
val pluginName = plugin::class.simpleName ?: plugin.toString()
scope.launch {
runCatchingSafely {
logger.i("Start loading of $plugin")
logger.i("Start loading of $pluginName")
with(plugin) {
startPlugin(koin)
}
}.onFailure { e ->
logger.w("Unable to start plugin $plugin", e)
logger.w("Unable to start plugin $pluginName", e)
}.onSuccess {
logger.i("Complete loading of $plugin")
logger.i("Complete loading of $pluginName")
}
}
}.joinAll()