mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 22:39:25 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
59e0e751f1 | |||
a5ae5e6c2b | |||
a2b87e63c9 | |||
d95c283653 | |||
6a9ae0c148 | |||
040dd517d8 | |||
9663c1ca64 | |||
ffc2d23be7 | |||
49b009e59b | |||
778b6a555b | |||
b3730998e9 | |||
837758aebe | |||
6ac4149aa1 | |||
278584ae6a | |||
126f9d5f41 | |||
ce15ff4e0a | |||
382b956beb | |||
36deab4909 | |||
550fc59d9d | |||
9100a57458 | |||
74d9bbccd9 | |||
75e602a349 | |||
e73644db10 | |||
148e6bdae7 | |||
1b540199f0 | |||
30b70e9984 | |||
c1557cff27 | |||
2d662f91b3 | |||
c4a08e52e5 | |||
08c371c142 | |||
8e62dd460c | |||
1f9302dc94 | |||
16f445f699 | |||
b4abd564ec | |||
14ffafb0a7 | |||
e0cc780887 | |||
ab7d277167 | |||
8308c1df4d | |||
12c29f5180 | |||
2963098870 | |||
9f56b0a26d | |||
75851312fd | |||
3a3be138a5 | |||
fb7d1f18b0 | |||
d1c6c7696a | |||
5f38d9635d | |||
8185ea87b1 | |||
98bd07d025 | |||
c502c70a21 | |||
d933dc532b | |||
42594f0656 | |||
4b83ca19c3 | |||
bab13f5e83 | |||
14c5f5a26c | |||
b26a4f24d4 | |||
498ec673dc | |||
5c67ab6aec |
21
.github/workflows/dokka_push.yml
vendored
Normal file
21
.github/workflows/dokka_push.yml
vendored
Normal 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
|
16
.github/workflows/gradle_build.yml
vendored
16
.github/workflows/gradle_build.yml
vendored
@@ -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
24
.github/workflows/packages_push.yml
vendored
Normal 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 }}
|
93
CHANGELOG.md
93
CHANGELOG.md
@@ -1,5 +1,98 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.4.36
|
||||||
|
|
||||||
|
* All `Android` targets inside common mpp modules now includes JVM code
|
||||||
|
|
||||||
|
## 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
|
## 0.4.26
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
@@ -25,6 +25,15 @@ allprojects {
|
|||||||
google()
|
google()
|
||||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||||
|
if (it != rootProject.findProject("docs")) {
|
||||||
|
tasks.whenTaskAdded { task ->
|
||||||
|
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
|
||||||
|
task.enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "./extensions.gradle"
|
apply from: "./extensions.gradle"
|
||||||
|
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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(
|
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
noinline block: suspend (T) -> Unit
|
noinline block: suspend (T) -> Unit
|
||||||
) = subscribeSafely(
|
) = subscribe(scope) {
|
||||||
scope,
|
safelyWithoutExceptions {
|
||||||
{},
|
block(it)
|
||||||
block
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -20,11 +20,11 @@ allprojects {
|
|||||||
|
|
||||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||||
|
|
||||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization"
|
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
|
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject"
|
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||||
|
|
||||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings"
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||||
|
|
||||||
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||||
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
||||||
|
@@ -6,18 +6,18 @@ kotlin.incremental.js=true
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
||||||
kotlin_version=1.4.30
|
kotlin_version=1.4.32
|
||||||
kotlin_coroutines_version=1.4.2
|
kotlin_coroutines_version=1.4.3
|
||||||
kotlin_serialisation_core_version=1.1.0
|
kotlin_serialisation_core_version=1.1.0
|
||||||
kotlin_exposed_version=0.29.1
|
kotlin_exposed_version=0.30.2
|
||||||
|
|
||||||
ktor_version=1.5.1
|
ktor_version=1.5.3
|
||||||
|
|
||||||
klockVersion=2.0.6
|
klockVersion=2.0.7
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
uuidVersion=0.2.3
|
uuidVersion=0.2.4
|
||||||
|
|
||||||
# ANDROID
|
# ANDROID
|
||||||
|
|
||||||
@@ -39,10 +39,10 @@ crypto_js_version=4.0.0
|
|||||||
|
|
||||||
# Dokka
|
# Dokka
|
||||||
|
|
||||||
dokka_version=1.4.20
|
dokka_version=1.4.30
|
||||||
|
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.4.26
|
version=0.4.36
|
||||||
android_code_version=30
|
android_code_version=40
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -12,6 +12,22 @@ data class PaginationResult<T>(
|
|||||||
|
|
||||||
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0)
|
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(
|
fun <T> List<T>.createPaginationResult(
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
commonObjectsNumber: Long
|
commonObjectsNumber: Long
|
||||||
|
@@ -7,14 +7,17 @@ const val defaultMediumPageSize = 5
|
|||||||
const val defaultLargePageSize = 10
|
const val defaultLargePageSize = 10
|
||||||
const val defaultExtraLargePageSize = 15
|
const val defaultExtraLargePageSize = 15
|
||||||
|
|
||||||
|
var defaultPaginationPageSize = defaultMediumPageSize
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE", "FunctionName")
|
@Suppress("NOTHING_TO_INLINE", "FunctionName")
|
||||||
inline fun FirstPagePagination(size: Int = defaultMediumPageSize) =
|
inline fun FirstPagePagination(size: Int = defaultPaginationPageSize) =
|
||||||
SimplePagination(
|
SimplePagination(
|
||||||
page = 0,
|
page = 0,
|
||||||
size = size
|
size = size
|
||||||
)
|
)
|
||||||
|
|
||||||
val emptyPagination = Pagination(0, 0)
|
val emptyPagination = Pagination(0, 0)
|
||||||
|
val firstPageWithOneElementPagination = FirstPagePagination(1)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun Pagination.nextPage() =
|
inline fun Pagination.nextPage() =
|
||||||
|
@@ -21,8 +21,10 @@ inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty())
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun PaginationResult<*>.thisPageIfNotEmpty(): Pagination? = if (results.isNotEmpty()) {
|
inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = if (results.isNotEmpty()) {
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()
|
||||||
|
@@ -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
|
||||||
|
)
|
||||||
|
}
|
@@ -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
|
||||||
|
)
|
@@ -36,6 +36,6 @@ class PaginatedIterable<T>(
|
|||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> makeIterable(
|
inline fun <T> makeIterable(
|
||||||
noinline countGetter: () -> Long,
|
noinline countGetter: () -> Long,
|
||||||
pageSize: Int = defaultMediumPageSize,
|
pageSize: Int = defaultPaginationPageSize,
|
||||||
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
|
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||||
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)
|
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)
|
||||||
|
@@ -18,6 +18,6 @@ val Pagination.asUrlQueryArrayParts
|
|||||||
val Map<String, String?>.extractPagination: Pagination
|
val Map<String, String?>.extractPagination: Pagination
|
||||||
get() = SimplePagination(
|
get() = SimplePagination(
|
||||||
get(paginationPageKey) ?.toIntOrNull() ?: 0,
|
get(paginationPageKey) ?.toIntOrNull() ?: 0,
|
||||||
get(paginationSizeKey) ?.toIntOrNull() ?: defaultMediumPageSize
|
get(paginationSizeKey) ?.toIntOrNull() ?: defaultPaginationPageSize
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import io.ktor.http.Parameters
|
|||||||
val Parameters.extractPagination: Pagination
|
val Parameters.extractPagination: Pagination
|
||||||
get() = SimplePagination(
|
get() = SimplePagination(
|
||||||
get("page") ?.toIntOrNull() ?: 0,
|
get("page") ?.toIntOrNull() ?: 0,
|
||||||
get("size") ?.toIntOrNull() ?: defaultMediumPageSize
|
get("size") ?.toIntOrNull() ?: defaultPaginationPageSize
|
||||||
)
|
)
|
||||||
|
|
||||||
val ApplicationCall.extractPagination: Pagination
|
val ApplicationCall.extractPagination: Pagination
|
||||||
|
@@ -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":"It is set of projects with micro tools for avoiding of routines coding","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","includeGpgSigning":true,"publishToMavenCentral":true,"developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}
|
|
@@ -12,11 +12,11 @@ publishing {
|
|||||||
pom {
|
pom {
|
||||||
description = "It is set of projects with micro tools for avoiding of routines coding"
|
description = "It is set of projects with micro tools for avoiding of routines coding"
|
||||||
name = "${project.name}"
|
name = "${project.name}"
|
||||||
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
|
url = "https://github.com/InsanusMokrassar/MicroUtils/"
|
||||||
|
|
||||||
scm {
|
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"
|
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
|
||||||
url = "ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git"
|
url = "https://github.com/InsanusMokrassar/MicroUtils.git"
|
||||||
}
|
}
|
||||||
|
|
||||||
developers {
|
developers {
|
||||||
@@ -40,33 +40,33 @@ publishing {
|
|||||||
|
|
||||||
license {
|
license {
|
||||||
name = "Apache Software License 2.0"
|
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 {
|
repositories {
|
||||||
maven {
|
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
|
||||||
name = "bintray"
|
maven {
|
||||||
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"
|
||||||
credentials {
|
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
||||||
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
credentials {
|
||||||
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 {
|
maven {
|
||||||
name = "sonatype"
|
name = "sonatype"
|
||||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||||
credentials {
|
credentials {
|
||||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
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')
|
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
publish.kpsb
Normal file
1
publish.kpsb
Normal 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
18
repos/cache/build.gradle
vendored
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CRUDCacheRepo.kt
vendored
Normal file
24
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CRUDCacheRepo.kt
vendored
Normal 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)
|
||||||
|
}
|
41
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KVCache.kt
vendored
Normal file
41
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KVCache.kt
vendored
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
20
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt
vendored
Normal file
20
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValueCacheRepo.kt
vendored
Normal 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)
|
||||||
|
}
|
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt
vendored
Normal file
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/KeyValuesCacheRepo.kt
vendored
Normal 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)
|
||||||
|
}
|
1
repos/cache/src/main/AndroidManifest.xml
vendored
Normal file
1
repos/cache/src/main/AndroidManifest.xml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.repos.cache"/>
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||||
@@ -12,14 +13,12 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
|||||||
suspend fun count(k: Key): Long
|
suspend fun count(k: Key): Long
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
|
|
||||||
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> = mutableListOf<Value>().also { list ->
|
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> {
|
||||||
doWithPagination {
|
val results = getAllWithNextPaging { get(k, it) }
|
||||||
get(k, it).also {
|
return if (reversed) {
|
||||||
list.addAll(it.results)
|
results.reversed()
|
||||||
}.nextPageIfNotEmpty()
|
} else {
|
||||||
}
|
results
|
||||||
if (reversed) {
|
|
||||||
list.reverse()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
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> {
|
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
|
||||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||||
doWithPagination {
|
doAllWithCurrentPaging {
|
||||||
keys(v, it).also {
|
keys(v, it).also {
|
||||||
unset(it.results)
|
unset(it.results)
|
||||||
}
|
}
|
||||||
|
@@ -1,31 +1,33 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
package dev.inmo.micro_utils.repos.pagination
|
||||||
|
|
||||||
import dev.inmo.micro_utils.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
|
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(
|
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
|
||||||
block: (List<T>) -> Unit
|
crossinline block: (List<T>) -> Unit
|
||||||
) {
|
) {
|
||||||
doWithPagination {
|
doForAllWithNextPaging {
|
||||||
methodCaller(it).also {
|
methodCaller(it).also {
|
||||||
block(it.results)
|
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(
|
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
|
||||||
block: (List<T>) -> Unit
|
crossinline block: (List<T>) -> Unit
|
||||||
) = doForAll({ getByPagination(it) }, block)
|
) = doForAllWithNextPaging {
|
||||||
|
getByPagination(it).also { block(it.results) }
|
||||||
|
}
|
||||||
|
|
||||||
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
|
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||||
): List<T> {
|
): List<T> = getAllWithNextPaging {
|
||||||
val resultList = mutableListOf<T>()
|
methodCaller(this, it)
|
||||||
doForAll(methodCaller) {
|
|
||||||
resultList.addAll(it)
|
|
||||||
}
|
|
||||||
return resultList
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
package dev.inmo.micro_utils.repos.pagination
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
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(
|
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
|
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(
|
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||||
block: (List<Pair<Key, Value>>) -> Unit
|
block: (List<Pair<Key, Value>>) -> Unit
|
||||||
) = doForAll({ keys(it, false) }, block)
|
) = doForAll({ keys(it, false) }, block)
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
|
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||||
): List<Pair<Key, Value>> {
|
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
||||||
val resultList = mutableListOf<Pair<Key, Value>>()
|
val result = methodCaller(it)
|
||||||
doForAll(methodCaller) {
|
result.changeResultsUnchecked(
|
||||||
resultList.addAll(it)
|
result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) }
|
||||||
}
|
)
|
||||||
return resultList
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
package dev.inmo.micro_utils.repos.pagination
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
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(
|
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
|
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(
|
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||||
block: (List<Pair<Key, List<Value>>>) -> Unit
|
block: (List<Pair<Key, List<Value>>>) -> Unit
|
||||||
) = doForAll({ keys(it, false) }, block)
|
) = doForAll({ keys(it, false) }, block)
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
|
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||||
): List<Pair<Key, List<Value>>> {
|
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
||||||
val resultList = mutableListOf<Pair<Key, List<Value>>>()
|
val keysResult = methodCaller(it)
|
||||||
doForAll(methodCaller) {
|
keysResult.changeResultsUnchecked(
|
||||||
resultList.addAll(it)
|
keysResult.results.map { k ->
|
||||||
}
|
k to getAll(k)
|
||||||
return resultList
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@@ -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(
|
fun SQLiteDatabase.select(
|
||||||
|
@@ -32,7 +32,9 @@ private object ContextsPool {
|
|||||||
|
|
||||||
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
|
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
|
||||||
try {
|
try {
|
||||||
block(it)
|
safely {
|
||||||
|
block(it)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
freeContext(it)
|
freeContext(it)
|
||||||
}
|
}
|
||||||
@@ -48,11 +50,12 @@ class TransactionContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
|
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||||
return coroutineContext[TransactionContext] ?.let {
|
coroutineContext[TransactionContext] ?.let {
|
||||||
withContext(it.databaseContext) {
|
return withContext(it.databaseContext) {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
} ?: ContextsPool.use { context ->
|
}
|
||||||
|
return ContextsPool.use { context ->
|
||||||
withContext(TransactionContext(context) + context) {
|
withContext(TransactionContext(context) + context) {
|
||||||
beginTransaction()
|
beginTransaction()
|
||||||
safely(
|
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 {
|
return when {
|
||||||
inTransaction() -> block()
|
inTransaction() -> block()
|
||||||
else -> {
|
else -> {
|
||||||
beginTransaction()
|
beginTransaction()
|
||||||
try {
|
try {
|
||||||
block().also {
|
block().also { setTransactionSuccessful() }
|
||||||
setTransactionSuccessful()
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
endTransaction()
|
endTransaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> SQLiteDatabase.blockingTransaction(block: SQLiteDatabase.() -> T): T = inlineTransaction(block)
|
||||||
|
@@ -31,6 +31,20 @@ class StandardSQLHelper(
|
|||||||
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
|
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 {
|
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
|
||||||
return writableDatabase.transaction(block)
|
return writableDatabase.transaction(block)
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
|||||||
it.count
|
it.count
|
||||||
}.toLong()
|
}.toLong()
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = helper.readableTransaction {
|
override suspend fun contains(id: IdType): Boolean = helper.blockingReadableTransaction {
|
||||||
select(
|
select(
|
||||||
tableName,
|
tableName,
|
||||||
null,
|
null,
|
||||||
|
@@ -8,7 +8,8 @@ import kotlinx.coroutines.flow.*
|
|||||||
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
helper: StandardSQLHelper
|
helper: StandardSQLHelper
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
|
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
|
||||||
|
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
||||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
||||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(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
|
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
|
||||||
|
|
||||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||||
val indexes = helper.writableTransaction {
|
val valuesContentValues = values.map { it.asContentValues() }
|
||||||
values.map {
|
val indexes = helper.blockingWritableTransaction {
|
||||||
insert(tableName, null, it.asContentValues())
|
valuesContentValues.map {
|
||||||
|
insert(tableName, null, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return helper.readableTransaction {
|
return helper.readableTransaction {
|
||||||
@@ -47,7 +49,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
|
|
||||||
override suspend fun deleteById(ids: List<IdType>) {
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
val deleted = mutableListOf<IdType>()
|
val deleted = mutableListOf<IdType>()
|
||||||
helper.writableTransaction {
|
helper.blockingWritableTransaction {
|
||||||
ids.forEach { id ->
|
ids.forEach { id ->
|
||||||
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
|
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
|
||||||
if (it > 0) {
|
if (it > 0) {
|
||||||
@@ -64,7 +66,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||||
val asContentValues = value.asContentValues(id)
|
val asContentValues = value.asContentValues(id)
|
||||||
if (asContentValues.keySet().isNotEmpty()) {
|
if (asContentValues.keySet().isNotEmpty()) {
|
||||||
helper.writableTransaction {
|
helper.blockingWritableTransaction {
|
||||||
update(
|
update(
|
||||||
tableName,
|
tableName,
|
||||||
asContentValues,
|
asContentValues,
|
||||||
@@ -79,11 +81,12 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||||
|
val contentValues = values.map { (id, value) -> id to value.asContentValues(id) }
|
||||||
helper.writableTransaction {
|
helper.writableTransaction {
|
||||||
values.forEach { (id, value) ->
|
contentValues.forEach { (id, contentValues) ->
|
||||||
update(
|
update(
|
||||||
tableName,
|
tableName,
|
||||||
value.asContentValues(id),
|
contentValues,
|
||||||
"$idColumnName=?",
|
"$idColumnName=?",
|
||||||
arrayOf(id.asId)
|
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() } }
|
||||||
}
|
}
|
@@ -42,21 +42,19 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
|
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
runBlocking(DatabaseCoroutineContext) {
|
helper.blockingWritableTransaction {
|
||||||
helper.writableTransaction {
|
createTable(
|
||||||
createTable(
|
tableName,
|
||||||
tableName,
|
internalId to internalIdType,
|
||||||
internalId to internalIdType,
|
idColumnName to ColumnType.Text.NOT_NULLABLE,
|
||||||
idColumnName to ColumnType.Text.NOT_NULLABLE,
|
valueColumnName to ColumnType.Text.NULLABLE
|
||||||
valueColumnName to ColumnType.Text.NULLABLE
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
val added = mutableListOf<Pair<Key, Value>>()
|
val added = mutableListOf<Pair<Key, Value>>()
|
||||||
helper.writableTransaction {
|
helper.blockingWritableTransaction {
|
||||||
toAdd.forEach { (k, values) ->
|
toAdd.forEach { (k, values) ->
|
||||||
values.forEach { v ->
|
values.forEach { v ->
|
||||||
insert(
|
insert(
|
||||||
@@ -78,7 +76,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear(k: Key) {
|
override suspend fun clear(k: Key) {
|
||||||
helper.writableTransaction {
|
helper.blockingWritableTransaction {
|
||||||
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
|
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
|
||||||
}.also {
|
}.also {
|
||||||
if (it > 0) {
|
if (it > 0) {
|
||||||
@@ -88,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||||
val (clearedKeys, inserted) = helper.writableTransaction {
|
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
|
||||||
toSet.mapNotNull { (k, _) ->
|
toSet.mapNotNull { (k, _) ->
|
||||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
|
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
|
||||||
k
|
k
|
||||||
@@ -110,13 +108,13 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
inserted.forEach { newPair -> _onNewValue.emit(newPair) }
|
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 {
|
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||||
it.count > 0
|
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(
|
select(
|
||||||
tableName,
|
tableName,
|
||||||
selection = "$idColumnName=? AND $valueColumnName=?",
|
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(
|
select(
|
||||||
tableName
|
tableName
|
||||||
).use {
|
).use {
|
||||||
@@ -135,7 +133,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
}.toLong()
|
}.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 {
|
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||||
it.count
|
it.count
|
||||||
}
|
}
|
||||||
@@ -147,7 +145,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
reversed: Boolean
|
reversed: Boolean
|
||||||
): PaginationResult<Value> = count(k).let { count ->
|
): PaginationResult<Value> = count(k).let { count ->
|
||||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||||
helper.readableTransaction {
|
helper.blockingReadableTransaction {
|
||||||
select(
|
select(
|
||||||
tableName,
|
tableName,
|
||||||
selection = "$idColumnName=?",
|
selection = "$idColumnName=?",
|
||||||
@@ -173,7 +171,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
reversed: Boolean
|
reversed: Boolean
|
||||||
): PaginationResult<Key> = count().let { count ->
|
): PaginationResult<Key> = count().let { count ->
|
||||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||||
helper.readableTransaction {
|
helper.blockingReadableTransaction {
|
||||||
select(
|
select(
|
||||||
tableName,
|
tableName,
|
||||||
limit = resultPagination.limitClause()
|
limit = resultPagination.limitClause()
|
||||||
@@ -198,7 +196,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
reversed: Boolean
|
reversed: Boolean
|
||||||
): PaginationResult<Key> = count().let { count ->
|
): PaginationResult<Key> = count().let { count ->
|
||||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||||
helper.readableTransaction {
|
helper.blockingReadableTransaction {
|
||||||
select(
|
select(
|
||||||
tableName,
|
tableName,
|
||||||
selection = "$valueColumnName=?",
|
selection = "$valueColumnName=?",
|
||||||
@@ -220,7 +218,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
helper.writableTransaction {
|
helper.blockingWritableTransaction {
|
||||||
toRemove.flatMap { (k, vs) ->
|
toRemove.flatMap { (k, vs) ->
|
||||||
vs.mapNotNullA { v ->
|
vs.mapNotNullA { v ->
|
||||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
|
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.StandardCRUDRepo
|
||||||
|
|
||||||
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
flowsChannelsSize: Int = 0,
|
flowsChannelsSize: Int = 0,
|
||||||
tableName: String = ""
|
tableName: String = ""
|
||||||
@@ -8,4 +10,5 @@ abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
flowsChannelsSize,
|
flowsChannelsSize,
|
||||||
tableName
|
tableName
|
||||||
),
|
),
|
||||||
ExposedCRUDRepo<ObjectType, IdType>
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
|
StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
@@ -60,9 +60,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
if (it > 0) {
|
if (it > 0) {
|
||||||
select {
|
transaction(db = database) {
|
||||||
selectById(this, id)
|
select {
|
||||||
}.limit(1).firstOrNull() ?.asObject
|
selectById(this, id)
|
||||||
|
}.limit(1).firstOrNull() ?.asObject
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
7
serialization/typed_serializer/build.gradle
Normal file
7
serialization/typed_serializer/build.gradle
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
@@ -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)
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.serialization.typed_serializer"/>
|
@@ -11,6 +11,7 @@ String[] includes = [
|
|||||||
":pagination:ktor:server",
|
":pagination:ktor:server",
|
||||||
":mime_types",
|
":mime_types",
|
||||||
":repos:common",
|
":repos:common",
|
||||||
|
":repos:cache",
|
||||||
":repos:exposed",
|
":repos:exposed",
|
||||||
":repos:inmemory",
|
":repos:inmemory",
|
||||||
":repos:ktor:client",
|
":repos:ktor:client",
|
||||||
@@ -25,6 +26,7 @@ String[] includes = [
|
|||||||
":android:alerts:recyclerview",
|
":android:alerts:recyclerview",
|
||||||
":serialization:base64",
|
":serialization:base64",
|
||||||
":serialization:encapsulator",
|
":serialization:encapsulator",
|
||||||
|
":serialization:typed_serializer",
|
||||||
|
|
||||||
":dokka"
|
":dokka"
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user