Compare commits

...

68 Commits

Author SHA1 Message Date
040dd517d8 hotfixes 2021-04-15 14:30:45 +06:00
ffc2d23be7 add project typed serializer 2021-04-15 14:15:40 +06:00
49b009e59b update exposed 2021-04-15 13:49:53 +06:00
778b6a555b start 0.4.35 2021-04-15 13:48:52 +06:00
b3730998e9 Merge pull request #58 from InsanusMokrassar/0.4.34
0.4.34
2021-04-13 12:28:25 +06:00
837758aebe update uuid 2021-04-13 12:22:04 +06:00
6ac4149aa1 fixes in crud repos 2021-04-13 12:20:24 +06:00
278584ae6a start 0.4.34 2021-04-13 12:10:55 +06:00
126f9d5f41 Merge pull request #57 from InsanusMokrassar/0.4.33
0.4.33
2021-04-05 16:30:58 +06:00
ce15ff4e0a update ktor 2021-04-05 16:22:39 +06:00
382b956beb make createWeakSubScope private, upfill readme 2021-04-05 16:20:11 +06:00
36deab4909 weak jobs workaround 2021-04-05 16:10:36 +06:00
550fc59d9d start 0.4.33 2021-04-05 15:14:21 +06:00
9100a57458 Merge pull request #56 from InsanusMokrassar/0.4.32
0.4.32
2021-04-03 14:07:01 +06:00
74d9bbccd9 updates 2021-04-02 18:43:56 +06:00
75e602a349 update kotlin exposed 2021-04-02 18:39:56 +06:00
e73644db10 start 0.4.32 2021-04-02 18:26:53 +06:00
148e6bdae7 Merge pull request #55 from InsanusMokrassar/0.4.31
0.4.31
2021-03-29 20:12:11 +06:00
1b540199f0 small update 2021-03-29 19:51:38 +06:00
30b70e9984 replacement of doForAll and getAll 2021-03-29 19:48:37 +06:00
c1557cff27 huge update with crud caching, common doForAll/getAll and deprecations in old ones doForAll 2021-03-29 19:39:10 +06:00
2d662f91b3 updates in coroutines 2021-03-29 17:57:36 +06:00
c4a08e52e5 update kotlin 2021-03-24 13:11:15 +06:00
08c371c142 add repos.cache 2021-03-24 13:01:15 +06:00
8e62dd460c start 0.4.31 2021-03-23 13:46:35 +06:00
1f9302dc94 Merge pull request #54 from InsanusMokrassar/0.4.30
0.4.30
2021-03-17 14:33:15 +06:00
16f445f699 update klock 2021-03-17 13:52:54 +06:00
b4abd564ec update paginations 2021-03-17 13:48:58 +06:00
14ffafb0a7 start 0.4.30 2021-03-17 13:43:52 +06:00
e0cc780887 Merge pull request #52 from InsanusMokrassar/0.4.29
0.4.29
2021-03-05 20:45:34 +06:00
ab7d277167 add note about transactions improvements 2021-03-05 20:42:30 +06:00
8308c1df4d update gradlewrapper up to 6.8.3 2021-03-05 19:12:26 +06:00
12c29f5180 Update packages_push.yml 2021-03-05 18:37:10 +06:00
2963098870 update publishing scripts 2021-03-05 16:43:50 +06:00
9f56b0a26d skip signing on publishing to packages 2021-03-05 16:34:16 +06:00
75851312fd remove old build yml 2021-03-05 16:24:43 +06:00
3a3be138a5 add packages publishing yml 2021-03-05 16:24:22 +06:00
fb7d1f18b0 update publication scripts 2021-03-05 16:21:36 +06:00
d1c6c7696a android database improvements 2021-03-05 16:15:21 +06:00
5f38d9635d update version of coroutines 2021-03-05 13:05:38 +06:00
8185ea87b1 start 0.4.29 2021-03-05 13:04:19 +06:00
98bd07d025 Merge pull request #50 from InsanusMokrassar/0.4.28
0.4.28
2021-03-02 01:09:48 +06:00
c502c70a21 should fix lint errors 2021-03-02 00:54:21 +06:00
d933dc532b add actions actor 2021-03-01 18:35:11 +06:00
42594f0656 update dependencies 2021-03-01 18:23:20 +06:00
4b83ca19c3 start 0.4.28 2021-03-01 18:21:34 +06:00
bab13f5e83 Update CHANGELOG.md 2021-02-26 15:35:15 +06:00
14c5f5a26c Merge pull request #48 from InsanusMokrassar/0.4.27
0.4.27
2021-02-23 13:09:04 +06:00
b26a4f24d4 fix in AbstractExposedWriteCRUDRepo 2021-02-23 12:51:13 +06:00
498ec673dc start 0.4.27 2021-02-23 12:50:55 +06:00
5c67ab6aec Merge pull request #47 from InsanusMokrassar/0.4.26
0.4.26
2021-02-21 21:36:09 +06:00
f78359b5d7 update serialization 2021-02-21 21:19:13 +06:00
dbce612cb2 start 0.4.26 2021-02-21 21:16:45 +06:00
4322ffdb0a Merge pull request #46 from InsanusMokrassar/0.4.25
0.4.25
2021-02-11 15:12:39 +06:00
123b848d74 update matrix test 2021-02-11 15:12:21 +06:00
b120fbd2b1 remove add and put column method in matrix row builder 2021-02-11 15:07:24 +06:00
d41c3c2de4 updates in matrix 2021-02-11 15:05:11 +06:00
a3cabd7a9e add matrix 2021-02-11 14:43:07 +06:00
3dde486126 start 0.4.25 2021-02-11 14:31:05 +06:00
5978122dda update scripts 2021-02-06 00:51:31 +06:00
4b4806ce34 Merge pull request #45 from InsanusMokrassar/0.4.24
0.4.24
2021-02-05 16:11:09 +06:00
987c6cc709 update android version 2021-02-05 16:10:08 +06:00
012c7e9bdf add inline functions in DoWithFirstBuilder 2021-02-05 16:09:08 +06:00
d06bb265e5 update publishing scripts 2021-02-05 15:47:12 +06:00
e2fb8bf21e update depdendencies 2021-02-05 15:42:46 +06:00
60dea2a518 DoWithFirstBuilder 2021-02-05 15:38:20 +06:00
3877f49278 start 0.4.24 2021-02-05 15:35:09 +06:00
a299a5b505 Merge pull request #44 from InsanusMokrassar/0.4.23
0.4.23
2021-01-28 15:45:24 +06:00
56 changed files with 982 additions and 157 deletions

21
.github/workflows/dokka_push.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Publish KDocs
on:
push:
branches:
- master
jobs:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build
run: ./gradlew dokkaHtml
- name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dokka/build/dokka/html
publish_branch: kdocs

View File

@@ -1,16 +0,0 @@
name: Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build

24
.github/workflows/packages_push.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Publish package to GitHub Packages
on: [push]
jobs:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
rm gradle.properties
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
- name: Publish
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signMetadataPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
env:
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,5 +1,113 @@
# Changelog
## 0.4.35
* `Versions`:
* `Kotlin Exposed`: `0.30.1` -> `0.30.2`
* `Serialization`:
* `TypedSerializer`:
* Project has been inited
## 0.4.34
* `Versions`:
* `uuid`: `0.2.3` -> `0.2.4`
* `Repos`:
* `AbstractExposedCRUDRepo` now implements `StandardCRUDRepo`
* `AbstractMutableAndroidCRUDRepo` now implements `StandardCRUDRepo`
## 0.4.33
* `Versions`:
* `Ktor`: `1.5.2` -> `1.5.3`
* `Coroutines`
* Add `WeakJob` workaround:
* `CoroutineScope#weakLaunch`
* `CoroutineScope#weakAsync`
## 0.4.32
* `Versions`:
* `Kotlin Exposed`: `0.29.1` -> `0.30.1`
## 0.4.31
* `Versions`:
* `Kotlin`: `1.4.31` -> `1.4.32`
* `Pagination`:
* New extensions `PaginationResult.changeResultsUnchecked` and `PaginationResult.changeResults` for mapping results
with the same parameters, but different data
* Extension `PaginationResult.thisPageIfNotEmpty` now is typed and will return `PaginationResult?` with the same
generic type as income `PaginationResult`
* New extension `PaginationResult.currentPageIfNotEmpty` - shortcut for `PaginationResult.thisPageIfNotEmpty`
* New common functions. They were created as replacements for currently available for more comfortable work
with repos pagination:
* `doForAll`
* `doForAllWithNextPaging`
* `doForAllWithCurrentPaging`
* `getAll`
* `getAllWithNextPaging`
* `getAllWithCurrentPaging`
* `Coroutines`:
* Rewrite `subscribeSafelyWithoutExceptions`
* Now `subscribeSafelyWithoutExceptions` will use default handler instead of skipping
* New extension `subscribeSafelySkippingExceptions`
* `Repos`
* New subproject `repos.cache` - this subproject will contain repos with data caching mechanisms
* Most old `doForAll` methods have been deprecated
## 0.4.30
* `Versions`:
* `Klock`: `2.0.6` -> `2.0.7`
* `Pagination`:
* New variable `defaultPaginationPageSize` has been added to be able to change default pagination size
* Add new value `firstPageWithOneElementPagination`
## 0.4.29
* `Versions`:
* `Coroutines`: `1.4.2` -> `1.4.3`
* `Repos`:
* `Common`
* `Android`:
* New `blockingReadableTransaction`/`blockingWritableTransaction`
* Android databases realizations now use blocking transactions where it is possible
* Several improvements in transactions work
## 0.4.28
* `Versions`:
* `Kotlin`: `1.4.30` -> `1.4.31`
* `Ktor`: `1.5.1` -> `1.5.2`
* `Coroutines`
* Add `createActionsActor`/`createSafeActionsActor` and `doWithSuspending`
## 0.4.27
* `Repos`
* `Exposed`
* Fix in `AbstractExposedWriteCRUDRepo`
## 0.4.26
* `Versions`:
* `Serialization`: `1.0.1` -> `1.1.0`
## 0.4.25
* `Matrix`:
* Subproject has been created
## 0.4.24
* `Versions`:
* `Kotlin`: `1.4.21` -> `1.4.30`
* `Klock`: `2.0.4` -> `2.0.6`
* `Coroutines`:
* New class `DoWithFirstBuilder`
* Several new extensions like `firstOf`/`first`/`invokeOnFirstOf`
## 0.4.23
* `Versions`:

View File

@@ -0,0 +1,46 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlin.coroutines.*
interface ActorAction<T> {
suspend operator fun invoke(): T
}
/**
* Planned to use with [doWithSuspending]. Will execute incoming lambdas sequentially
*
* @see actor
*/
fun CoroutineScope.createActionsActor() = actor<suspend () -> Unit> {
it()
}
/**
* Planned to use with [doWithSuspending]. Will execute incoming lambdas sequentially
*
* @see safeActor
*/
inline fun CoroutineScope.createSafeActionsActor(
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler
) = safeActor<suspend () -> Unit>(Channel.UNLIMITED, onException) {
it()
}
/**
* Must be use with actor created by [createActionsActor] or [createSafeActionsActor]. Will send lambda which will
* execute [action] and return result.
*
* @see suspendCoroutine
* @see safely
*/
suspend fun <T> Channel<suspend () -> Unit>.doWithSuspending(
action: ActorAction<T>
) = suspendCoroutine<T> {
offer {
safely({ e -> it.resumeWithException(e) }) {
it.resume(action())
}
}
}

View File

@@ -9,6 +9,19 @@ class DeferredAction<T, O>(
suspend operator fun invoke() = callback(deferred.await())
}
class DoWithFirstBuilder<T>(
private val scope: CoroutineScope
) {
private val deferreds = mutableListOf<Deferred<T>>()
operator fun plus(block: suspend CoroutineScope.() -> T) {
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
}
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
fun build() = deferreds.toList()
}
fun <T, O> Deferred<T>.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback)
suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
@@ -32,9 +45,41 @@ suspend fun <T, O> Iterable<Deferred<T>>.invokeOnFirst(
callback: suspend (T) -> O
): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult)
suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit,
callback: suspend (T) -> O
) = firstOf(
DoWithFirstBuilder<T>(this).apply(block).build(),
cancelOnResult
).let { callback(it) }
suspend fun <T, O> invokeOnFirst(
scope: CoroutineScope,
vararg variants: Deferred<T>,
cancelOnResult: Boolean = true,
callback: suspend (T) -> O
): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback)
suspend fun <T> CoroutineScope.firstOf(
variants: Iterable<Deferred<T>>,
cancelOnResult: Boolean = true
) = variants.invokeOnFirst(this, cancelOnResult) { it }
suspend fun <T> CoroutineScope.firstOf(
cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit
) = firstOf(
DoWithFirstBuilder<T>(this).apply(block).build(),
cancelOnResult
)
suspend fun <T> CoroutineScope.firstOf(
vararg variants: Deferred<T>,
cancelOnResult: Boolean = true
) = firstOf(variants.toList(), cancelOnResult)
suspend fun <T> List<Deferred<T>>.first(
scope: CoroutineScope,
cancelOnResult: Boolean = true
) = scope.firstOf(this, cancelOnResult)

View File

@@ -25,13 +25,25 @@ inline fun <T> Flow<T>.subscribeSafely(
}
/**
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
* Use [subscribeSafelyWithoutExceptions], but all exceptions will be passed to [defaultSafelyExceptionHandler]
*/
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
scope: CoroutineScope,
noinline block: suspend (T) -> Unit
) = subscribeSafely(
scope,
{},
block
)
) = subscribe(scope) {
safelyWithoutExceptions {
block(it)
}
}
/**
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
*/
inline fun <T> Flow<T>.subscribeSafelySkippingExceptions(
scope: CoroutineScope,
noinline block: suspend (T) -> Unit
) = subscribe(scope) {
safelyWithoutExceptions({ /* skip exceptions */ }) {
block(it)
}
}

View File

@@ -0,0 +1,31 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope ->
coroutineContext.job.invokeOnCompletion { newScope.cancel() }
}
fun CoroutineScope.weakLaunch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val scope = createWeakSubScope()
val job = scope.launch(context, start, block)
job.invokeOnCompletion { scope.cancel() }
return job
}
fun <T> CoroutineScope.weakAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val scope = createWeakSubScope()
val deferred = scope.async(context, start, block)
deferred.invokeOnCompletion { scope.cancel() }
return deferred
}

View File

@@ -0,0 +1,40 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import org.junit.Test
class WeakJob {
@Test
fun `test that weak jobs works correctly`() {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
scope.launchSynchronously {
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = weakLaunch {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = weakAsync {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
}
assert(!weakLaunchJob.isActive)
assert(!weakAsyncJob.isActive)
}
}

View File

@@ -6,18 +6,18 @@ kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.4.21
kotlin_coroutines_version=1.4.2
kotlin_serialisation_core_version=1.0.1
kotlin_exposed_version=0.29.1
kotlin_version=1.4.32
kotlin_coroutines_version=1.4.3
kotlin_serialisation_core_version=1.1.0
kotlin_exposed_version=0.30.2
ktor_version=1.5.1
ktor_version=1.5.3
klockVersion=2.0.4
klockVersion=2.0.7
github_release_plugin_version=2.2.12
uuidVersion=0.2.3
uuidVersion=0.2.4
# ANDROID
@@ -39,10 +39,10 @@ crypto_js_version=4.0.0
# Dokka
dokka_version=1.4.20
dokka_version=1.4.30
# Project data
group=dev.inmo
version=0.4.23
android_code_version=27
version=0.4.35
android_code_version=39

View File

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

7
matrix/build.gradle Normal file
View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.matrix
typealias Matrix<T> = List<Row<T>>

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.matrix
class MatrixBuilder<T> {
private val mutMatrix: MutableList<List<T>> = ArrayList()
val matrix: Matrix<T>
get() = mutMatrix
fun row(t: List<T>) = mutMatrix.add(t)
operator fun List<T>.unaryPlus() = row(this)
}
fun <T> MatrixBuilder<T>.row(block: RowBuilder<T>.() -> Unit) = +RowBuilder<T>().also(block).row
fun <T> MatrixBuilder<T>.row(vararg elements: T) = +elements.toList()

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.matrix
fun <T> matrix(block: MatrixBuilder<T>.() -> Unit): Matrix<T> {
return MatrixBuilder<T>().also(block).matrix
}
fun <T> flatMatrix(block: RowBuilder<T>.() -> Unit): Matrix<T> {
return MatrixBuilder<T>().apply {
row(block)
}.matrix
}
fun <T> flatMatrix(vararg elements: T): Matrix<T> {
return MatrixBuilder<T>().apply {
row { elements.forEach { +it } }
}.matrix
}

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.matrix
typealias Row<T> = List<T>

View File

@@ -0,0 +1,14 @@
package dev.inmo.micro_utils.matrix
class RowBuilder<T> {
private val mutRow: MutableList<T> = ArrayList()
val row: Row<T>
get() = mutRow
fun column(t: T) = mutRow.add(t)
operator fun T.unaryPlus() = column(this)
}
fun <T> row(block: RowBuilder<T>.() -> Unit): List<T> = RowBuilder<T>().also(block).row
fun <T> RowBuilder<T>.columns(elements: List<T>) = elements.forEach(::column)
fun <T> RowBuilder<T>.columns(vararg elements: T) = elements.forEach(::column)

View File

@@ -0,0 +1,45 @@
package dev.inmo.micro_utils.matrix
import kotlin.test.Test
import kotlin.test.assertEquals
class SimpleTest {
@Test
fun simpleTest() {
val expected = listOf(
listOf(1, 2, 3),
listOf(4, 5, 6)
)
val fromMatrixWithVarargs = matrix<Int> {
row(1, 2, 3)
row(4, 5, 6)
}
val fromMatrixWithColumnsVarargs = matrix<Int> {
row {
columns(1, 2, 3)
}
row {
columns(4, 5, 6)
}
}
val fromMatrixWithSimpleAdd = matrix<Int> {
row {
column(1)
column(2)
column(3)
}
row {
column(4)
column(5)
column(6)
}
}
assertEquals(expected, fromMatrixWithVarargs)
assertEquals(expected, fromMatrixWithColumnsVarargs)
assertEquals(expected, fromMatrixWithSimpleAdd)
}
}

View File

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

View File

@@ -4,7 +4,9 @@ project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm()
jvm {
compilations.main.kotlinOptions.useIR = true
}
sourceSets {
commonMain {

View File

@@ -4,7 +4,9 @@ project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm()
jvm {
compilations.main.kotlinOptions.useIR = true
}
js (BOTH) {
browser()
nodejs()

View File

@@ -12,6 +12,22 @@ data class PaginationResult<T>(
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0)
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality
*/
fun <I, O> PaginationResult<I>.changeResultsUnchecked(
data: List<O>
): PaginationResult<O> = PaginationResult(page, pagesNumber, data, size)
/**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/
fun <I, O> PaginationResult<I>.changeResults(
data: List<O>
): PaginationResult<O> {
require(data.size == results.size)
return changeResultsUnchecked(data)
}
fun <T> List<T>.createPaginationResult(
pagination: Pagination,
commonObjectsNumber: Long

View File

@@ -7,14 +7,17 @@ const val defaultMediumPageSize = 5
const val defaultLargePageSize = 10
const val defaultExtraLargePageSize = 15
var defaultPaginationPageSize = defaultMediumPageSize
@Suppress("NOTHING_TO_INLINE", "FunctionName")
inline fun FirstPagePagination(size: Int = defaultMediumPageSize) =
inline fun FirstPagePagination(size: Int = defaultPaginationPageSize) =
SimplePagination(
page = 0,
size = size
)
val emptyPagination = Pagination(0, 0)
val firstPageWithOneElementPagination = FirstPagePagination(1)
@Suppress("NOTHING_TO_INLINE")
inline fun Pagination.nextPage() =

View File

@@ -21,8 +21,10 @@ inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty())
}
@Suppress("NOTHING_TO_INLINE")
inline fun PaginationResult<*>.thisPageIfNotEmpty(): Pagination? = if (results.isNotEmpty()) {
inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = if (results.isNotEmpty()) {
this
} else {
null
}
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()

View File

@@ -0,0 +1,35 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
suspend fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
block: suspend (Pagination) -> PaginationResult<T>
) {
doWithPagination(initialPagination) {
block(it).let(paginationMapper)
}
}
suspend fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
) {
doForAll(
initialPagination,
{ it.nextPageIfNotEmpty() },
block
)
}
suspend fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
) {
doForAll(
initialPagination,
{ it.currentPageIfNotEmpty() },
block
)
}

View File

@@ -0,0 +1,35 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
suspend fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
block: suspend (Pagination) -> PaginationResult<T>
): List<T> {
val results = mutableListOf<T>()
doForAll(initialPagination, paginationMapper) {
block(it).also {
results.addAll(it.results)
}
}
return results.toList()
}
suspend fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.nextPageIfNotEmpty() },
block
)
suspend fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.currentPageIfNotEmpty() },
block
)

View File

@@ -36,6 +36,6 @@ class PaginatedIterable<T>(
@Suppress("NOTHING_TO_INLINE")
inline fun <T> makeIterable(
noinline countGetter: () -> Long,
pageSize: Int = defaultMediumPageSize,
pageSize: Int = defaultPaginationPageSize,
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)

View File

@@ -18,6 +18,6 @@ val Pagination.asUrlQueryArrayParts
val Map<String, String?>.extractPagination: Pagination
get() = SimplePagination(
get(paginationPageKey) ?.toIntOrNull() ?: 0,
get(paginationSizeKey) ?.toIntOrNull() ?: defaultMediumPageSize
get(paginationSizeKey) ?.toIntOrNull() ?: defaultPaginationPageSize
)

View File

@@ -6,7 +6,7 @@ import io.ktor.http.Parameters
val Parameters.extractPagination: Pagination
get() = SimplePagination(
get("page") ?.toIntOrNull() ?: 0,
get("size") ?.toIntOrNull() ?: defaultMediumPageSize
get("size") ?.toIntOrNull() ?: defaultPaginationPageSize
)
val ApplicationCall.extractPagination: Pagination

View File

@@ -1 +0,0 @@
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}

View File

@@ -1,37 +1,22 @@
apply plugin: 'maven-publish'
apply plugin: 'signing'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
task sourceJar (type : Jar) {
classifier = 'sources'
}
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}"
artifact sourceJar
} else {
artifactId = "${project.name}-$name"
}
}
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
description = "It is set of projects with micro tools for avoiding of routines coding"
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
url = "https://github.com/InsanusMokrassar/MicroUtils/"
scm {
developerConnection = "scm:git:[fetch=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git[push=]ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
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 {
@@ -55,22 +40,37 @@ publishing {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
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 = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/MicroUtils/${project.name}/;publish=1;override=1")
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
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('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')
}
}
}
}
}
}
signing {
useGpgCmd()
sign publishing.publications
}

1
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","includeGpgSigning":true,"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":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}]}}

18
repos/cache/build.gradle vendored Normal file
View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api internalProject("micro_utils.repos.common")
api internalProject("micro_utils.repos.inmemory")
}
}
}
}

View File

@@ -0,0 +1,24 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
protected val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
protected val kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected val idGetter: (ObjectType) -> IdType
) : CRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
kvCache.set(id, it)
})
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
}

View File

@@ -0,0 +1,41 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
interface KVCache<K, V> : KeyValueRepo<K, V>
open class SimpleKVCache<K, V>(
protected val cachedValuesCount: Int,
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
protected open val cacheStack = ArrayList<K>(cachedValuesCount)
protected val syncMutex = Mutex()
protected suspend fun makeUnset(toUnset: List<K>) {
cacheStack.removeAll(toUnset)
kvParent.unset(toUnset)
}
override suspend fun set(toSet: Map<K, V>) {
syncMutex.withLock {
if (toSet.size > cachedValuesCount) {
cacheStack.clear()
kvParent.unset(getAllWithNextPaging { kvParent.keys(it) })
val keysToInclude = toSet.keys.drop(toSet.size - cachedValuesCount)
cacheStack.addAll(keysToInclude)
kvParent.set(keysToInclude.associateWith { toSet.getValue(it) })
} else {
makeUnset(cacheStack.take(toSet.size))
}
}
}
override suspend fun unset(toUnset: List<K>) {
syncMutex.withLock { makeUnset(toUnset) }
}
}

View File

@@ -0,0 +1,20 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class KeyValueCacheRepo<Key,Value>(
protected val parentRepo: KeyValueRepo<Key, Value>,
protected val kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : KeyValueRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
}

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class KeyValuesCacheRepo<Key,Value>(
protected val parentRepo: KeyValuesRepo<Key, Value>,
protected val kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : KeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return 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
} ?: parentRepo.get(k, pagination, reversed)
}
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
return kvCache.get(k) ?.let {
if (reversed) it.reversed() else it
} ?: parentRepo.getAll(k, reversed)
}
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.repos.cache"/>

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
@@ -12,14 +13,12 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun count(k: Key): Long
suspend fun count(): Long
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> = mutableListOf<Value>().also { list ->
doWithPagination {
get(k, it).also {
list.addAll(it.results)
}.nextPageIfNotEmpty()
}
if (reversed) {
list.reverse()
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> {
val results = getAllWithNextPaging { get(k, it) }
return if (reversed) {
results.reversed()
} else {
results
}
}

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
import kotlinx.coroutines.flow.Flow
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
@@ -41,7 +42,7 @@ suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithV
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
doWithPagination {
doAllWithCurrentPaging {
keys(v, it).also {
unset(it.results)
}

View File

@@ -1,31 +1,33 @@
package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
@Deprecated("Will be removed soon due to redundancy. Can be replaced with other doForAll extensions")
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
block: (List<T>) -> Unit
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
crossinline block: (List<T>) -> Unit
) {
doWithPagination {
doForAllWithNextPaging {
methodCaller(it).also {
block(it.results)
}.nextPageIfNotEmpty()
}
}
}
@Deprecated("Will be removed soon due to redundancy. Can be replaced with other doForAll extensions")
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
block: (List<T>) -> Unit
) = doForAll({ getByPagination(it) }, block)
crossinline block: (List<T>) -> Unit
) = doForAllWithNextPaging {
getByPagination(it).also { block(it.results) }
}
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
): List<T> {
val resultList = mutableListOf<T>()
doForAll(methodCaller) {
resultList.addAll(it)
}
return resultList
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging {
methodCaller(this, it)
}

View File

@@ -1,8 +1,10 @@
package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
@@ -15,17 +17,17 @@ suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REP
}
}
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
block: (List<Pair<Key, Value>>) -> Unit
) = doForAll({ keys(it, false) }, block)
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, Value>> {
val resultList = mutableListOf<Pair<Key, Value>>()
doForAll(methodCaller) {
resultList.addAll(it)
}
return resultList
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, Value>> = getAllWithNextPaging {
val result = methodCaller(it)
result.changeResultsUnchecked(
result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) }
)
}

View File

@@ -1,8 +1,10 @@
package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
@@ -25,17 +27,19 @@ suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> RE
}
}
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
block: (List<Pair<Key, List<Value>>>) -> Unit
) = doForAll({ keys(it, false) }, block)
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, List<Value>>> {
val resultList = mutableListOf<Pair<Key, List<Value>>>()
doForAll(methodCaller) {
resultList.addAll(it)
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
val keysResult = methodCaller(it)
keysResult.changeResultsUnchecked(
keysResult.results.map { k ->
k to getAll(k)
}
return resultList
)
}

View File

@@ -26,20 +26,39 @@ fun SQLiteDatabase.createTable(
}
}
fun Cursor.getString(columnName: String) = getString(
getColumnIndex(columnName)
/**
* @throws IllegalArgumentException
*/
fun Cursor.getString(columnName: String): String = getString(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getLong(columnName: String) = getLong(
getColumnIndex(columnName)
/**
* @throws IllegalArgumentException
*/
fun Cursor.getShort(columnName: String): Short = getShort(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getInt(columnName: String) = getInt(
getColumnIndex(columnName)
/**
* @throws IllegalArgumentException
*/
fun Cursor.getLong(columnName: String): Long = getLong(
getColumnIndexOrThrow(columnName)
)
fun Cursor.getDouble(columnName: String) = getDouble(
getColumnIndex(columnName)
/**
* @throws IllegalArgumentException
*/
fun Cursor.getInt(columnName: String): Int = getInt(
getColumnIndexOrThrow(columnName)
)
/**
* @throws IllegalArgumentException
*/
fun Cursor.getDouble(columnName: String): Double = getDouble(
getColumnIndexOrThrow(columnName)
)
fun SQLiteDatabase.select(

View File

@@ -32,7 +32,9 @@ private object ContextsPool {
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
try {
safely {
block(it)
}
} finally {
freeContext(it)
}
@@ -48,11 +50,12 @@ class TransactionContext(
}
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
return coroutineContext[TransactionContext] ?.let {
withContext(it.databaseContext) {
coroutineContext[TransactionContext] ?.let {
return withContext(it.databaseContext) {
block()
}
} ?: ContextsPool.use { context ->
}
return ContextsPool.use { context ->
withContext(TransactionContext(context) + context) {
beginTransaction()
safely(
@@ -70,18 +73,18 @@ suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T
}
}
inline fun <T> SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T {
inline fun <T> SQLiteDatabase.inlineTransaction(crossinline block: SQLiteDatabase.() -> T): T {
return when {
inTransaction() -> block()
else -> {
beginTransaction()
try {
block().also {
setTransactionSuccessful()
}
block().also { setTransactionSuccessful() }
} finally {
endTransaction()
}
}
}
}
fun <T> SQLiteDatabase.blockingTransaction(block: SQLiteDatabase.() -> T): T = inlineTransaction(block)

View File

@@ -31,6 +31,20 @@ class StandardSQLHelper(
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
}
fun <T> SQLiteOpenHelper.blockingWritableTransaction(block: SQLiteDatabase.() -> T): T {
return writableDatabase.blockingTransaction(block)
}
fun <T> SQLiteOpenHelper.blockingReadableTransaction(block: SQLiteDatabase.() -> T): T {
return readableDatabase.blockingTransaction(block)
}
fun <T> StandardSQLHelper.blockingWritableTransaction(block: SQLiteDatabase.() -> T): T {
return sqlOpenHelper.blockingWritableTransaction(block)
}
fun <T> StandardSQLHelper.blockingReadableTransaction(block: SQLiteDatabase.() -> T): T {
return sqlOpenHelper.blockingReadableTransaction(block)
}
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
return writableDatabase.transaction(block)
}

View File

@@ -20,7 +20,7 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
it.count
}.toLong()
override suspend fun contains(id: IdType): Boolean = helper.readableTransaction {
override suspend fun contains(id: IdType): Boolean = helper.blockingReadableTransaction {
select(
tableName,
null,

View File

@@ -8,7 +8,8 @@ import kotlinx.coroutines.flow.*
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
helper: StandardSQLHelper
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
@@ -19,9 +20,10 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
val indexes = helper.writableTransaction {
values.map {
insert(tableName, null, it.asContentValues())
val valuesContentValues = values.map { it.asContentValues() }
val indexes = helper.blockingWritableTransaction {
valuesContentValues.map {
insert(tableName, null, it)
}
}
return helper.readableTransaction {
@@ -47,7 +49,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
override suspend fun deleteById(ids: List<IdType>) {
val deleted = mutableListOf<IdType>()
helper.writableTransaction {
helper.blockingWritableTransaction {
ids.forEach { id ->
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
if (it > 0) {
@@ -64,7 +66,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
val asContentValues = value.asContentValues(id)
if (asContentValues.keySet().isNotEmpty()) {
helper.writableTransaction {
helper.blockingWritableTransaction {
update(
tableName,
asContentValues,
@@ -79,11 +81,12 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
}
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
val contentValues = values.map { (id, value) -> id to value.asContentValues(id) }
helper.writableTransaction {
values.forEach { (id, value) ->
contentValues.forEach { (id, contentValues) ->
update(
tableName,
value.asContentValues(id),
contentValues,
"$idColumnName=?",
arrayOf(id.asId)
)
@@ -98,5 +101,5 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
}
}
override suspend fun count(): Long = helper.readableTransaction { select(tableName).use { it.count.toLong() } }
override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } }
}

View File

@@ -42,8 +42,7 @@ class OneToManyAndroidRepo<Key, Value>(
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
init {
runBlocking(DatabaseCoroutineContext) {
helper.writableTransaction {
helper.blockingWritableTransaction {
createTable(
tableName,
internalId to internalIdType,
@@ -52,11 +51,10 @@ class OneToManyAndroidRepo<Key, Value>(
)
}
}
}
override suspend fun add(toAdd: Map<Key, List<Value>>) {
val added = mutableListOf<Pair<Key, Value>>()
helper.writableTransaction {
helper.blockingWritableTransaction {
toAdd.forEach { (k, values) ->
values.forEach { v ->
insert(
@@ -78,7 +76,7 @@ class OneToManyAndroidRepo<Key, Value>(
}
override suspend fun clear(k: Key) {
helper.writableTransaction {
helper.blockingWritableTransaction {
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
}.also {
if (it > 0) {
@@ -88,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
val (clearedKeys, inserted) = helper.writableTransaction {
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
toSet.mapNotNull { (k, _) ->
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
k
@@ -110,13 +108,13 @@ class OneToManyAndroidRepo<Key, Value>(
inserted.forEach { newPair -> _onNewValue.emit(newPair) }
}
override suspend fun contains(k: Key): Boolean = helper.readableTransaction {
override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
it.count > 0
}
}
override suspend fun contains(k: Key, v: Value): Boolean = helper.readableTransaction {
override suspend fun contains(k: Key, v: Value): Boolean = helper.blockingReadableTransaction {
select(
tableName,
selection = "$idColumnName=? AND $valueColumnName=?",
@@ -127,7 +125,7 @@ class OneToManyAndroidRepo<Key, Value>(
}
}
override suspend fun count(): Long =helper.readableTransaction {
override suspend fun count(): Long =helper.blockingReadableTransaction {
select(
tableName
).use {
@@ -135,7 +133,7 @@ class OneToManyAndroidRepo<Key, Value>(
}
}.toLong()
override suspend fun count(k: Key): Long = helper.readableTransaction {
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
it.count
}
@@ -147,7 +145,7 @@ class OneToManyAndroidRepo<Key, Value>(
reversed: Boolean
): PaginationResult<Value> = count(k).let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction {
helper.blockingReadableTransaction {
select(
tableName,
selection = "$idColumnName=?",
@@ -173,7 +171,7 @@ class OneToManyAndroidRepo<Key, Value>(
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction {
helper.blockingReadableTransaction {
select(
tableName,
limit = resultPagination.limitClause()
@@ -198,7 +196,7 @@ class OneToManyAndroidRepo<Key, Value>(
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction {
helper.blockingReadableTransaction {
select(
tableName,
selection = "$valueColumnName=?",
@@ -220,7 +218,7 @@ class OneToManyAndroidRepo<Key, Value>(
}
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
helper.writableTransaction {
helper.blockingWritableTransaction {
toRemove.flatMap { (k, vs) ->
vs.mapNotNullA { v ->
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {

View File

@@ -1,5 +1,7 @@
package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.StandardCRUDRepo
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0,
tableName: String = ""
@@ -8,4 +10,5 @@ abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize,
tableName
),
ExposedCRUDRepo<ObjectType, IdType>
ExposedCRUDRepo<ObjectType, IdType>,
StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -60,9 +60,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
}
}.let {
if (it > 0) {
transaction(db = database) {
select {
selectById(this, id)
}.limit(1).firstOrNull() ?.asObject
}
} else {
null
}

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,74 @@
package dev.inmo.micro_utils.serialization.typed_serializer
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlin.reflect.KClass
open class TypedSerializer<T : Any>(
kClass: KClass<T>,
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
) : KSerializer<T> {
protected val serializers = presetSerializers.toMutableMap()
@InternalSerializationApi
override open val descriptor: SerialDescriptor = buildSerialDescriptor(
"TextSourceSerializer",
SerialKind.CONTEXTUAL
) {
element("type", String.serializer().descriptor)
element("value", ContextualSerializer(kClass).descriptor)
}
@InternalSerializationApi
override open fun deserialize(decoder: Decoder): T {
return decoder.decodeStructure(descriptor) {
var type: String? = null
lateinit var result: T
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> type = decodeStringElement(descriptor, 0)
1 -> {
require(type != null) { "Type is null, but it is expected that was inited already" }
result = decodeSerializableElement(
descriptor,
1,
serializers.getValue(type)
)
}
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
result
}
}
@InternalSerializationApi
protected open fun <O: T> CompositeEncoder.encode(value: O) {
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
}
@InternalSerializationApi
override open fun serialize(encoder: Encoder, value: T) {
encoder.encodeStructure(descriptor) {
val valueSerializer = value::class.serializer()
val type = serializers.keys.first { serializers[it] == valueSerializer }
encodeStringElement(descriptor, 0, type)
encode(value)
}
}
open fun <O: T> include(type: String, serializer: KSerializer<O>) {
serializers[type] = serializer
}
open fun exclude(type: String) {
serializers.remove(type)
}
}
inline fun <reified T : Any> TypedSerializer(
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
) = TypedSerializer(T::class, presetSerializers)

View File

@@ -0,0 +1,40 @@
package dev.inmo.micro_utils.serialization.typed_serializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
class TypedSerializerTests {
interface Example {
val number: Number
}
val serialFormat = Json { }
@Serializable
data class Example1(override val number: Long) : Example
@Serializable
data class Example2(override val number: Double) : Example
@Test
fun testThatSerializerWorksCorrectly() {
val serializer = TypedSerializer(
mapOf(
"long" to Example1.serializer(),
"double" to Example2.serializer()
)
)
val value1 = Example1(Random.nextLong())
val value2 = Example2(Random.nextDouble())
val list = listOf(value1, value2)
val serialized = serialFormat.encodeToString(ListSerializer(serializer), list)
val deserialized = serialFormat.decodeFromString(ListSerializer(serializer), serialized)
assertEquals(list, deserialized)
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.serialization.typed_serializer"/>

View File

@@ -2,6 +2,7 @@ rootProject.name='micro_utils'
String[] includes = [
":common",
":matrix",
":crypto",
":selector:common",
":pagination:common",
@@ -10,6 +11,7 @@ String[] includes = [
":pagination:ktor:server",
":mime_types",
":repos:common",
":repos:cache",
":repos:exposed",
":repos:inmemory",
":repos:ktor:client",
@@ -24,6 +26,7 @@ String[] includes = [
":android:alerts:recyclerview",
":serialization:base64",
":serialization:encapsulator",
":serialization:typed_serializer",
":dokka"
]