Compare commits

...

102 Commits
0.2.5 ... 0.4.9

Author SHA1 Message Date
f3bec34882 add Synchronously 2020-12-02 17:28:59 +06:00
2d5304a770 BroadcastChannel -> MutableSharedFlow in repos 2020-12-02 16:39:04 +06:00
88f2c16c82 crud exposed realization open fun asObject 2020-12-02 16:28:08 +06:00
490c318d1c update ktor 2020-12-02 16:21:31 +06:00
8beaf61a08 start 0.4.9 2020-12-02 16:13:54 +06:00
8b61c984eb Merge pull request #25 from InsanusMokrassar/0.4.8
0.4.8
2020-12-01 15:52:07 +06:00
e38094df58 change order in iterable to skip changing of pagination on no left elements error 2020-12-01 15:48:37 +06:00
c25e3f5867 update versions 2020-12-01 15:38:44 +06:00
f78e81d175 PaginatedIterable 2020-12-01 15:34:12 +06:00
3837ae237d start 0.4.8 2020-12-01 15:11:18 +06:00
2b6ef8b4ff Merge pull request #24 from InsanusMokrassar/0.4.7
0.4.7
2020-11-27 19:30:40 +06:00
6cc0eefb3e upfix of default in includeWebsocketHandling 2020-11-27 17:53:29 +06:00
ab11e28bf7 UnifiedRouter 2020-11-27 14:35:00 +06:00
26d5f5a5f5 UnifiedRequester 2020-11-27 13:30:02 +06:00
74ae91cba6 start 0.4.7 2020-11-27 11:57:40 +06:00
70509c7edd update wrapper 2020-11-26 22:50:44 +06:00
5a69bd6c63 Merge pull request #23 from InsanusMokrassar/0.4.6
0.4.6
2020-11-26 22:50:08 +06:00
091bb1394f Warning annotation 2020-11-26 18:01:12 +06:00
b82c3864a0 typealiases for repos 2020-11-26 15:32:00 +06:00
49ee38a936 FileStandardKeyValueRepo 2020-11-26 15:03:40 +06:00
c201866c51 FlowCollector#invoke 2020-11-26 15:02:30 +06:00
023f841fb5 Pagination#isFirstPage 2020-11-26 15:01:45 +06:00
76102e9ab3 start 0.4.6 2020-11-26 15:00:32 +06:00
b2fc5e2a4d add query params docs 2020-11-25 11:09:54 +06:00
55aacb8753 update publish script 2020-11-23 22:46:40 +06:00
8702846216 Merge pull request #22 from InsanusMokrassar/0.4.5
0.4.5
2020-11-23 18:48:57 +06:00
3347a6d189 Update build.gradle 2020-11-23 18:46:52 +06:00
47b3b42949 replaces 2020-11-23 18:32:14 +06:00
e985631621 add recyclerview.alerts project 2020-11-23 18:10:53 +06:00
e15034bfa4 add tools to android 2020-11-23 17:39:17 +06:00
e5dbcd25dd remove kotlin android extensions 2020-11-23 17:27:52 +06:00
3714c02c12 add Android.Alerts project 2020-11-23 17:24:02 +06:00
fa636b4146 start 0.4.5 2020-11-23 17:15:21 +06:00
3d825aebc3 Update LeftItems.kt 2020-11-23 01:18:30 +06:00
629884a396 update left items 2020-11-23 00:36:37 +06:00
e7a0fa4e8f Merge pull request #21 from InsanusMokrassar/0.4.4
0.4.4
2020-11-22 23:47:04 +06:00
1a41f37a9d update changelog 2020-11-22 23:42:01 +06:00
07a65e0bb5 fixes 2020-11-22 23:19:16 +06:00
615f7f99c3 one more time update mechanism of sql transactions in android 2020-11-22 22:00:50 +06:00
3de5558ed4 new mechanism of transactions 2020-11-22 21:54:51 +06:00
6bbe3a271f update defaults in VersionsRepo 2020-11-22 19:27:53 +06:00
8bee29f683 update version of klock and remove gradle bintray plugin 2020-11-22 19:12:10 +06:00
c40f0fdcb9 VersionsRepo 2020-11-22 19:07:37 +06:00
30d1453a12 start 0.4.4 2020-11-22 17:49:39 +06:00
75fa88b00d Merge pull request #20 from InsanusMokrassar/0.4.3
0.4.3
2020-11-20 13:28:44 +06:00
0f817ad212 update kotlin 2020-11-20 13:10:40 +06:00
d7b46ae0d4 getSp and getDp 2020-11-20 10:23:40 +06:00
b6be14ecca start 0.4.3 2020-11-20 10:20:35 +06:00
33dbfc6f69 Merge pull request #19 from InsanusMokrassar/0.4.2
0.4.2
2020-11-18 20:32:42 +06:00
fcacdcd544 set method in realizations 2020-11-18 20:27:34 +06:00
dd2fc5a86f start 0.4.2 2020-11-18 20:26:08 +06:00
0f8a6f6bde update publishing files 2020-11-18 18:22:19 +06:00
1efd94181d Merge pull request #18 from InsanusMokrassar/0.4.1
0.4.1
2020-11-17 00:51:25 +06:00
71ff0232aa fix keyvalue exposed repo initiation 2020-11-16 20:05:22 +06:00
63921cd984 fix of shared flow size in ExposedKeyValueRepo 2020-11-16 19:48:28 +06:00
051e03bed3 start 0.4.1 2020-11-16 19:46:50 +06:00
a051394f4f Merge pull request #17 from InsanusMokrassar/0.4.0
0.4.0
2020-11-14 18:04:59 +06:00
b872babe45 new extensions withMapper 2020-11-14 17:35:11 +06:00
5a9cabc4bd add ReadStandardKeyValueRepo#keys with value parameter 2020-11-14 17:27:26 +06:00
3ba630684a new ReadOneToManyKeyValueRepo#keys 2020-11-14 16:44:28 +06:00
498cd12f94 start 0.4.0 2020-11-14 16:18:42 +06:00
062848f2e4 Merge pull request #16 from InsanusMokrassar/0.3.3
0.3.3
2020-11-12 22:20:11 +06:00
d4b4547718 plus for flows 2020-11-12 22:16:21 +06:00
22cd440dd7 start 0.3.3 2020-11-12 22:10:41 +06:00
6fc64526d4 update renaming of android parts 2020-11-11 16:29:43 +06:00
08075dfafe update min sdk for kotlin parts 2020-11-11 16:17:19 +06:00
efcb25622e fixes in gradle files 2020-11-11 16:06:44 +06:00
5ebf29d1fb Merge pull request #15 from InsanusMokrassar/0.3.2
0.3.2
2020-11-11 13:14:46 +06:00
b7d0ce3c97 formatting fix in keyvalue 2020-11-11 12:56:31 +06:00
e20929aec4 mappers workaround 2020-11-11 12:56:31 +06:00
f74167cc65 start 0.3.2 2020-11-11 12:56:31 +06:00
c55cd5342e update travis
trying to manually install packages in travis
2020-11-11 12:53:34 +06:00
237d89d611 experimentally update extension internalProject 2020-11-10 23:55:11 +06:00
aff9b52f1c update max heap size of jvm for gradle 2020-11-10 21:46:57 +06:00
d8cc6ed06e update max heap size of jvm for gradle 2020-11-10 21:46:19 +06:00
dffafa54c3 update publishing scripts 2020-11-10 20:53:08 +06:00
776a3af497 Merge pull request #14 from InsanusMokrassar/0.3.1
0.3.1
2020-11-10 19:50:57 +06:00
5b3d9e8d64 fill changelog 2020-11-10 19:49:21 +06:00
90b7d74a0c update for correct work 2020-11-10 18:06:10 +06:00
5b596c76e0 updates of dokka 2020-11-10 17:10:32 +06:00
c59e601e2e update gradle script to include android 2020-11-10 16:12:10 +06:00
5ce71ee6f6 add android tools 2020-11-10 15:43:15 +06:00
97dadf517a successfuly add android project 2020-11-10 14:33:47 +06:00
a739e874bf partially fix build 2020-11-10 12:48:22 +06:00
c73cb14615 temporal version 2020-11-09 22:51:47 +06:00
d9464f7b90 start 0.3.1 2020-11-09 21:46:58 +06:00
aeee41680e Merge pull request #13 from InsanusMokrassar/0.3.0
0.3.0
2020-11-09 00:52:30 +06:00
c914f8c44a fixes 2020-11-09 00:51:08 +06:00
abfda3627c repos rework 2020-11-09 00:46:26 +06:00
5b5bfa02db complete with major version change 0.3.0 2020-11-08 23:50:53 +06:00
0fdb072385 update: Replace channels by sharedflows in exposed CRUD realization 2020-11-08 20:45:45 +06:00
7b4c9d59b0 start 0.2.8 2020-11-08 20:44:40 +06:00
bd5923716f Merge pull request #11 from InsanusMokrassar/0.2.7
0.2.7
2020-11-07 02:28:38 +06:00
64cc42a23b upfill of changelog 2020-11-07 02:27:54 +06:00
325f178763 repos update 2020-11-07 02:25:50 +06:00
fc8d0e52ef start 0.2.7 2020-11-04 18:47:42 +06:00
e58348907e Merge pull request #10 from InsanusMokrassar/0.2.6
0.2.6
2020-11-03 20:08:06 +06:00
eaba9173ae more fixes:) 2020-11-03 20:07:33 +06:00
2f42b30f87 fix for paginate (again) and lastIndexExclusive 2020-11-03 19:43:45 +06:00
35913b95be fixes in pagintion 2020-11-03 18:37:50 +06:00
8023fa1b76 start 0.2.6 2020-11-03 18:35:22 +06:00
4cbe2d1d61 Merge pull request #9 from InsanusMokrassar/0.2.5
0.2.5
2020-11-02 21:44:53 +06:00
134 changed files with 3993 additions and 800 deletions

1
.gitignore vendored
View File

@@ -10,5 +10,6 @@ build/
out/ out/
secret.gradle secret.gradle
local.properties
publishing.sh publishing.sh

View File

@@ -1,13 +1,27 @@
language: java language: android
install: true install: true
os: linux os: linux
dist: trusty dist: trusty
jdk: oraclejdk8 jdk: oraclejdk8
android:
components:
- tools
- platform-tools
- build-tools-30.0.2
- android-30
- add-on
- extra
before_script:
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
jobs: jobs:
include: include:
- stage: build - stage: build
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
- state: test # Tests are temporarily disabled on public travis due to the problems of launching
script: ./gradlew allTests # - state: test
# script: ./gradlew allTests

View File

@@ -1,5 +1,203 @@
# Changelog # Changelog
## 0.4.9
* `Versions`:
* `Ktor`: `1.4.2` -> `1.4.3`
* `Coroutines`:
* `launchSynchronously` has been added in JVM
* `Repo`
* `Common`
* In repos different usages of `BroadcastChannel`s has been replaced with `MutableSharedFlow`
* `Exposed`
* `asObject` open fun has been added in CRUD realization
## 0.4.8
* `Versions`:
* `Coroutines`: `1.4.1` -> `1.4.2`
* `UUID`: `0.2.2` -> `0.2.3`
* `Pagination`
* Add `PaginatedIterable` and `PaginatedIterator`
## 0.4.7
* `Ktor`
* `Client`
* New class `UnifiedRequester`
* `Server`
* New class `UnifiedRouter`
* `Repos`
* `Ktor`
* `Client`
* Rewriting of all clients on new `UnifiedRequester`
* `Server`
* Rewriting of all clients on new `UnifiedRouter`
## 0.4.6
* `Common`
* New annotation `Warning` has been added
* `Pagination`
* `Common`
* `Pagination` got new extension: `Pagination#isFirstPage`
* `Coroutines`:
* New extension `FlowCollector#invoke` has been added
* `Repos`
* `Common`
* `JVM` (and `Android` since `Android API 26`):
* `FileStandardKeyValueRepo` has been added
* Add several `typealias`es for each type of repos
## 0.4.5
* `Android`
* `Alerts`
* `Common`
* Project has been created
* `RecyclerView`
* Project has been created
* `Common`
* Annotation `PreviewFeature` has been added
* `Android`
* Added tools to work with visibility in more comfortable way
* Added tools to work with disabled/enabled state in more comfortable way
* Added tools to work with expanded/collapsed state in more comfortable way (in preview mode)
## 0.4.4
* `Versions`:
* `Klock`: `1.12.1` -> `2.0.0`
* `Commons`:
* Update left items functionality to include work with `GridLayoutManager`
* `Repos`:
* Add interface `VersionsRepo`
* Add default realization of `VersionsRepo` named `StandardVersionsRepo` which use `StandardVersionsRepoProxy`
to get access to some end-store
* Add default realization of `StandardVersionsRepoProxy` based on `KeyValue` repos
* Add realizations of `StandardVersionsRepoProxy` for exposed and android (`SQL` and `SharedPreferences`)
* `Commons`:
* In Android fully reworked transactions functions
* Now `DatabaseCoroutineContext` is a shortcut for `Dispatchers.IO`
## 0.4.3
* `Versions`:
* `Kotlin`: `1.4.10` -> `1.4.20`
* `Common`:
* Two new extensions for Android:
* `Resources#getSp`
* `Resources#getDp`
## 0.4.2
* `Repos`:
* Add `WriteOneToManyKeyValueRepo#set` function and extensions
## 0.4.1
* `Repos`:
* Fixed error in `ExposedKeyValueRepo` related to negative size of shared flow
* Fixed error in `ExposedKeyValueRepo` related to avoiding of table initiation
## 0.4.0
* `Repos`:
* `ReadOneToManyKeyValueRepo` got `keys` method with value parameter
* All implementations inside of this library has been updated
* `ReadStandardKeyValueRepo` got `keys` method with value parameter
* All implementations inside of this library has been updated
* New extensions `withMapper`
## 0.3.3
* `Coroutines`:
* New extension `Flow<T>#plus`
## 0.3.2
* `Versions`:
* `Coroutines`: `1.4.1` -> `1.4.2`
* `Repos`:
* `Common`:
* New inline function `mapper` for simplier creating of `MapperRepo` objects
* Extensions `withMapper` for keyvalue repos and onetomany repos
## 0.3.1
**ANDROID PACKAGES**
* `Android`:
* `RecyclerView`:
* Library has been created
* `Common`
* Now available package `dev.inmo:micro_utils.common-android`
* `Coroutines`
* Now available package `dev.inmo:micro_utils.coroutines-android`
* `Ktor`
* `Common`
* Now available package `dev.inmo:micro_utils.ktor.common-android`
* `Client`
* Now available package `dev.inmo:micro_utils.ktor.client-android`
* `MimeTypes`
* Now available package `dev.inmo:micro_utils.mime_types-android`
* `Pagination`
* `Common`
* Now available package `dev.inmo:micro_utils.pagination.common-android`
* `Ktor`
* `Common`
* Now available package `dev.inmo:micro_utils.pagination.ktor.common-android`
* `Repos`
* `Common`
* Now available package `dev.inmo:micro_utils.repos.common-android`
* Now it is possible to use default realizations of repos abstractions natively on android
* `Inmemory`
* Now available package `dev.inmo:micro_utils.repos.inmemory-android`
* `Ktor`
* `Common`
* Now available package `dev.inmo:micro_utils.repos.ktor.common-android`
* `Common`
* Now available package `dev.inmo:micro_utils.repos.ktor.client-android`
## 0.3.0
All deprecations has been removed
* `Repos`:
* `Common`:
* `KeyValue` and `OneToMany` repos lost their deprecated methods
* `OneToMany` write repos got additional extensions for mutation of repo
* `KeyValue` write repos got additional extensions for mutation of repo
* New interface `MapperRepo` and new classes which are using this:
* `KeyValue`
* `MapperReadStandardKeyValueRepo`
* `MapperWriteStandardKeyValueRepo`
* `MapperStandardKeyValueRepo`
* `OneToMany`
* `MapperReadOneToManyKeyValueRepo`
* `MapperWriteOneToManyKeyValueRepo`
* `MapperOneToManyKeyValueRepo`
* `Exposed`:
* CRUD realizations replaced their channels to shared flows
## 0.2.7
* `Versions`:
* `Coroutines`: `1.4.0` -> `1.4.1`
* `Repos`:
* `WriteStandardKeyValueRepo` got new methods `set` and `unset` with collections
* All standard realizations of repos got collections methods realizations
* All old usages of `BroadcastFlow` and `BroadcastChannel` has been replaced with `MutableSharedFlow`
* `Ktor`:
* `Server`:
* Fixed incorrect answer for `keyvalue`
## 0.2.6
* `Pagination`
* Fixes in function `List#paginate`
* Extension property `Pagination#lastIndexExclusive`
## 0.2.5 ## 0.2.5
* `Coroutines` * `Coroutines`

View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
androidMain {
dependencies {
api "androidx.appcompat:appcompat-resources:$appcompat_version"
}
}
}
}

View File

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

View File

@@ -0,0 +1,55 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
typealias AlertDialogCallback = (DialogInterface) -> Unit
inline fun Context.createAlertDialogTemplate(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null
): AlertDialog.Builder {
val builder = AlertDialog.Builder(this)
title ?.let {
builder.setTitle(title)
}
positivePair ?. let {
builder.setPositiveButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
negativePair ?. let {
builder.setNegativeButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
neutralPair ?. let {
builder.setNeutralButton(it.first) { di, _ -> it.second ?. invoke(di) }
}
return builder
}
inline fun Context.createAlertDialogTemplateWithResources(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null
): AlertDialog.Builder = createAlertDialogTemplate(
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second }
)
inline fun AlertDialog.setDismissChecker(noinline checker: () -> Boolean) : AlertDialog {
setOnDismissListener {
if (!checker()) {
show()
}
}
return this
}

View File

@@ -0,0 +1,38 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import android.view.View
inline fun <T: View> Context.createCustomViewAlertDialog(
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null,
show: Boolean = true,
viewCreator: (Context) -> T
): AlertDialog = createAlertDialogTemplate(
title, positivePair, neutralPair, negativePair
).apply {
setView(viewCreator(this@createCustomViewAlertDialog))
}.create().apply {
if (show) show()
}
inline fun <T: View> Context.createCustomViewAlertDialogWithResources(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true,
viewCreator: (Context) -> T
): AlertDialog = createCustomViewAlertDialog(
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second },
show,
viewCreator
)

View File

@@ -0,0 +1,45 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common
import android.app.AlertDialog
import android.content.Context
import androidx.annotation.StringRes
inline fun Context.createSimpleTextAlertDialog(
text: String,
title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null,
neutralPair: Pair<String, AlertDialogCallback?>? = null,
negativePair: Pair<String, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog = createAlertDialogTemplate(
title,
positivePair,
neutralPair,
negativePair
).apply {
setMessage(text)
}.create().apply {
if (show) {
show()
}
}
inline fun Context.createSimpleTextAlertDialog(
@StringRes
text: Int,
@StringRes
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog = createSimpleTextAlertDialog(
getString(text),
title ?.let { getString(it) },
positivePair ?.let { getString(it.first) to it.second },
neutralPair ?.let { getString(it.first) to it.second },
negativePair ?.let { getString(it.first) to it.second },
show
)

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
@file:Suppress("unused")
package dev.inmo.micro_utils.android.alerts.recyclerview
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.view.ViewGroup
import android.widget.TextView
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
import dev.inmo.micro_utils.android.recyclerview.*
data class AlertAction(
val title: String,
val callback: (DialogInterface) -> Unit
)
private class ActionViewHolder(
container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface
) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) {
private lateinit var action: AlertAction
private val textView: TextView
get() = itemView.findViewById(android.R.id.text1)
init {
itemView.setOnClickListener {
action.callback(dialogInterfaceGetter())
}
}
override fun onBind(item: AlertAction) {
action = item
textView.text = item.title
}
}
private class ActionsRecyclerViewAdapter(
data: List<AlertAction>,
private val dialogInterfaceGetter: () -> DialogInterface
) : RecyclerViewAdapter<AlertAction>(data) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
parent, dialogInterfaceGetter
)
}
fun Context.createActionsAlertDialog(
actions: List<AlertAction>,
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true
): AlertDialog {
lateinit var dialogInterface: DialogInterface
return createRecyclerViewDialog(
title, positivePair, neutralPair, negativePair, show
) {
ActionsRecyclerViewAdapter(
actions
) {
dialogInterface
}
}.also { dialogInterface = it }
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.android.alerts.recyclerview
import android.app.AlertDialog
import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dev.inmo.micro_utils.android.alerts.common.AlertDialogCallback
import dev.inmo.micro_utils.android.alerts.common.createCustomViewAlertDialogWithResources
fun Context.createRecyclerViewDialog(
title: Int? = null,
positivePair: Pair<Int, AlertDialogCallback?>? = null,
neutralPair: Pair<Int, AlertDialogCallback?>? = null,
negativePair: Pair<Int, AlertDialogCallback?>? = null,
show: Boolean = true,
layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this),
marginOfRecyclerView: Int = 8, // dp
recyclerViewSetUp: RecyclerView.() -> Unit = {},
adapterFactory: () -> RecyclerView.Adapter<*>
): AlertDialog {
val recyclerView = RecyclerView(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView, marginOfRecyclerView)
}
this.layoutManager = layoutManager
adapter = adapterFactory()
recyclerViewSetUp()
}
return createCustomViewAlertDialogWithResources(
title,
positivePair,
neutralPair,
negativePair,
show
) {
recyclerView
}
}

View File

@@ -0,0 +1,22 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
}
}
androidMain {
dependencies {
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
}
}
}
}

View File

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

View File

@@ -0,0 +1,22 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
abstract class AbstractStandardViewHolder<T>(
inflater: LayoutInflater,
container: ViewGroup?,
viewId: Int,
onViewInflated: ((View) -> Unit)? = null
) : AbstractViewHolder<T>(
inflater.inflate(viewId, container, false).also {
onViewInflated ?.invoke(it)
}
) {
constructor(
container: ViewGroup,
viewId: Int,
onViewInflated: ((View) -> Unit)? = null
) : this(LayoutInflater.from(container.context), container, viewId, onViewInflated)
}

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.View
import androidx.recyclerview.widget.RecyclerView
abstract class AbstractViewHolder<in T>(
view: View
) : RecyclerView.ViewHolder(view) {
abstract fun onBind(item: T)
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.android.recyclerview
import android.content.Context
import android.widget.LinearLayout
import androidx.recyclerview.widget.DividerItemDecoration
val Context.recyclerViewItemsDecoration
get() = DividerItemDecoration(this, LinearLayout.VERTICAL)

View File

@@ -0,0 +1,53 @@
package dev.inmo.micro_utils.android.recyclerview
import androidx.recyclerview.widget.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@Suppress("NOTHING_TO_INLINE")
private inline fun RecyclerView.LayoutManager.findLastVisibleItemPositionGetter(): (() -> Int)? = when (this) {
is LinearLayoutManager -> ::findLastVisibleItemPosition
is GridLayoutManager -> ::findLastVisibleItemPosition
else -> null
}
fun RecyclerView.lastVisibleItemFlow(
completingScope: CoroutineScope
): Flow<Int> {
val lastVisibleElementFun: () -> Int = layoutManager ?.findLastVisibleItemPositionGetter() ?: error("Currently supported only linear and grid layout manager")
val lastVisibleFlow = MutableStateFlow(lastVisibleElementFun())
addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
lastVisibleFlow.value = lastVisibleElementFun()
}
}.also { scrollListener ->
lastVisibleFlow.onCompletion {
removeOnScrollListener(scrollListener)
}.launchIn(completingScope)
}
)
return lastVisibleFlow.asStateFlow()
}
inline fun Flow<Int>.mapLeftItems(
crossinline countGetter: () -> Int
): Flow<Int> = map { countGetter() - it }
inline fun Flow<Int>.mapRequireFilling(
minimalLeftItems: Int,
crossinline countGetter: () -> Int
): Flow<Int> = mapLeftItems(countGetter).mapNotNull {
if (it < minimalLeftItems) {
it
} else {
null
}
}
inline fun RecyclerView.mapRequireFilling(
minimalLeftItems: Int,
completingScope: CoroutineScope,
crossinline countGetter: () -> Int
): Flow<Int> = lastVisibleItemFlow(completingScope).mapRequireFilling(minimalLeftItems, countGetter)

View File

@@ -0,0 +1,68 @@
package dev.inmo.micro_utils.android.recyclerview
import android.view.View
import androidx.recyclerview.widget.RecyclerView
abstract class RecyclerViewAdapter<T>(
val data: List<T>
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
var emptyView: View? = null
set(value) {
field = value
checkEmpty()
}
init {
registerAdapterDataObserver(
object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
checkEmpty()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
super.onItemRangeChanged(positionStart, itemCount, payload)
checkEmpty()
}
override fun onChanged() {
super.onChanged()
checkEmpty()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
super.onItemRangeRemoved(positionStart, itemCount)
checkEmpty()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
checkEmpty()
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
checkEmpty()
}
}
)
checkEmpty()
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: AbstractViewHolder<T>, position: Int) {
holder.onBind(data[position])
}
private fun checkEmpty() {
emptyView ?. let {
if (data.isEmpty()) {
it.visibility = View.VISIBLE
} else {
it.visibility = View.GONE
}
}
}
}

View File

@@ -1,16 +1,19 @@
buildscript { buildscript {
repositories { repositories {
mavenLocal()
jcenter() jcenter()
google()
mavenCentral() mavenCentral()
mavenLocal()
maven { url "https://plugins.gradle.org/m2/" } maven { url "https://plugins.gradle.org/m2/" }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version" classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
} }
} }
@@ -19,6 +22,7 @@ allprojects {
mavenLocal() mavenLocal()
jcenter() jcenter()
mavenCentral() mavenCentral()
google()
maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" }
} }
} }

View File

@@ -12,4 +12,7 @@ function assert_success() {
export RELEASE_MODE=true export RELEASE_MODE=true
project="$1" project="$1"
assert_success ./gradlew clean "$project:clean" "$project:build" "$project:publishToMavenLocal" "$project:bintrayUpload" assert_success ./gradlew clean
assert_success ./gradlew "$project:build"
assert_success ./gradlew "$project:publishToMavenLocal"
assert_success ./gradlew "$project:bintrayUpload"

View File

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

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.common
@RequiresOptIn(
"It is possible, that behaviour of this thing will be changed or removed in future releases",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class PreviewFeature
@RequiresOptIn(
"This thing is marked as warned. See message of warn to get more info",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class Warning(val message: String)

View File

@@ -149,18 +149,3 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
inline fun <T> Iterable<T>.calculateStrictDiff( inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T> other: Iterable<T>
) = calculateDiff(other, strictComparison = true) ) = calculateDiff(other, strictComparison = true)
/**
* Compare one-to-one
*/
@Deprecated("Will be removed or replaced with some new function. Use calculateDiff instead")
inline fun <T> Iterable<T>.syncWith(
other: Iterable<T>,
noinline removed: (List<T>) -> Unit = {},
noinline added: (List<T>) -> Unit = {}
) {
calculateDiff(other).also {
removed(it.removed.map { it.value })
added(it.added.map { it.value })
}
}

View File

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

View File

@@ -0,0 +1,13 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
import android.content.res.Resources
inline fun Resources.getSp(
resId: Int
) = getDimension(resId) / displayMetrics.scaledDensity
inline fun Resources.getDp(
resId: Int
) = getDimension(resId) * displayMetrics.density

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import android.view.View
import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.Transformation
@PreviewFeature
fun View.expand(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
measure(targetWidth, targetHeight)
val measuredHeight: Int = measuredHeight
layoutParams.height = 0
visibility = View.VISIBLE
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
super.applyTransformation(interpolatedTime, t)
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = duration
startAnimation(a)
}
@PreviewFeature
fun View.collapse(duration: Long = 500) {
val initialHeight: Int = measuredHeight
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
visibility = View.GONE
} else {
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
}
}
a.duration = duration
startAnimation(a)
}
@PreviewFeature
inline val View.isCollapsed
get() = visibility == View.GONE
@PreviewFeature
inline val View.isExpanded
get() = !isCollapsed
/**
* @return true in case of expanding
*/
@PreviewFeature
fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
expand(duration)
true
} else {
collapse(duration)
false
}

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.common
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
inline fun <T, R> Iterable<T>.mapNotNullA(transform: (T) -> R?): List<R> = map(transform).filter { it != null } as List<R>
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
inline fun <T, R> Array<T>.mapNotNullA(mapper: (T) -> R?): List<R> = map(mapper).filter { it != null } as List<R>

View File

@@ -0,0 +1,34 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.common
import android.view.View
import android.view.ViewGroup
inline val View.enabled
get() = isEnabled
inline val View.disabled
get() = !enabled
fun View.disable() {
if (this is ViewGroup) {
(0 until childCount).forEach { getChildAt(it).disable() }
}
isEnabled = false
}
fun View.enable() {
if (this is ViewGroup) {
(0 until childCount).forEach { getChildAt(it).enable() }
}
isEnabled = true
}
fun View.toggleEnabledState(enabled: Boolean) {
if (enabled) {
enable()
} else {
disable()
}
}

View File

@@ -0,0 +1,35 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.common
import android.view.View
inline val View.gone
get() = visibility == View.GONE
inline fun View.gone() {
visibility = View.GONE
}
inline val View.hidden
get() = visibility == View.INVISIBLE
inline fun View.hide() {
visibility = View.INVISIBLE
}
inline val View.shown
get() = visibility == View.VISIBLE
inline fun View.show() {
visibility = View.VISIBLE
}
fun View.toggleVisibility(goneOnHide: Boolean = true) {
if (isShown) {
if (goneOnHide) {
gone()
} else {
hide()
}
} else {
show()
}
}

View File

@@ -1,6 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
} }
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"
@@ -12,5 +13,10 @@ kotlin {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
} }
} }
androidMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
}
}
} }
} }

View File

@@ -1,24 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
@Suppress("FunctionName")
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
fun <T> BroadcastFlow(
internalChannelSize: Int = Channel.BUFFERED
): BroadcastFlow<T> {
val channel = BroadcastChannel<T>(internalChannelSize)
return BroadcastFlow(
channel,
channel.asFlow()
)
}
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
class BroadcastFlow<T> internal constructor(
private val channel: BroadcastChannel<T>,
private val flow: Flow<T>
): Flow<T> by flow, SendChannel<T> by channel

View File

@@ -1,68 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
const val defaultBroadcastStateFlowReplayCacheSize = 1
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
class BroadcastStateFlow<T> internal constructor(
parentFlow: Flow<T>,
initial: T,
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize,
replayScope: CoroutineScope
) : StateFlow<T>, Flow<T> by parentFlow {
private val deque = ArrayDeque<T>(1).also {
it.add(initial)
}
override val replayCache: List<T>
get() = deque.toList()
override val value: T
get() = deque.last()
init {
if (replayCacheSize < 1) {
error("Replay cache size can't be less than 1, but was $replayCacheSize")
}
parentFlow.onEach {
deque.addLast(it)
if (deque.size > replayCacheSize) {
deque.removeFirst()
}
}.launchIn(replayScope)
}
}
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
fun <T> BroadcastChannel<T>.asStateFlow(
value: T,
scope: CoroutineScope,
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
): StateFlow<T> = BroadcastStateFlow(asFlow(), value, replayCacheSize, scope)
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
fun <T> BroadcastChannel<T?>.asStateFlow(
scope: CoroutineScope,
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
): StateFlow<T?> = asStateFlow(null, scope, replayCacheSize)
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
fun <T> broadcastStateFlow(
initial: T, scope: CoroutineScope,
channelSize: Int = Channel.BUFFERED,
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
) = BroadcastChannel<T>(
channelSize
).let {
it to it.asStateFlow(initial, scope, replayCacheSize)
}
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
fun <T> broadcastStateFlow(
scope: CoroutineScope,
channelSize: Int = Channel.BUFFERED,
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
) = broadcastStateFlow<T?>(null, scope, channelSize, replayCacheSize)

View File

@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.FlowCollector
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)

View File

@@ -0,0 +1,8 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.merge
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)

View File

@@ -0,0 +1,28 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
fun <T> launchSynchronously(scope: CoroutineScope = CoroutineScope(Dispatchers.Default), block: suspend CoroutineScope.() -> T): T {
var throwable: Throwable? = null
var result: T? = null
val objectToSynchronize = java.lang.Object()
val launchCallback = {
scope.launch {
safely(
{
throwable = it
}
) {
result = block()
}
synchronized(objectToSynchronize) {
objectToSynchronize.notifyAll()
}
}
}
synchronized(objectToSynchronize) {
launchCallback()
objectToSynchronize.wait()
}
throw throwable ?: return result!!
}

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.coroutines
import kotlin.test.Test
import kotlin.test.assertEquals
class LaunchSynchronouslyTest {
@Test
fun testRunInCoroutine() {
(0 .. 10000).forEach {
assertEquals(it, launchSynchronously { it })
}
}
}

View File

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

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
Dispatchers.Main,
block
)

40
defaultAndroidSettings Normal file
View File

@@ -0,0 +1,40 @@
apply plugin: 'com.getkeepsafe.dexcount'
android {
compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion"
defaultConfig {
minSdkVersion "$android_minSdkVersion".toInteger()
targetSdkVersion "$android_compileSdkVersion".toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
}
buildTypes {
release {
minifyEnabled false
}
debug {
debuggable true
}
}
packagingOptions {
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}

View File

@@ -1,26 +1,14 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "org.jetbrains.dokka" version "$dokka_version" id "com.android.library"
id "org.jetbrains.dokka"
} }
repositories { repositories {
mavenLocal() mavenLocal()
jcenter() jcenter()
google()
mavenCentral() mavenCentral()
} }
@@ -30,6 +18,7 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
android {}
sourceSets { sourceSets {
commonMain { commonMain {
@@ -41,6 +30,57 @@ kotlin {
it != project it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) {
api it
}
}
}
}
jsMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
) {
api it
}
}
}
}
jvmMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
) {
api it
}
}
}
}
androidMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (
it != project
&& it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) { ) {
api it api it
} }
@@ -84,5 +124,11 @@ tasks.dokkaHtml {
named("jvmMain") { named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain")) sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
} }
named("androidMain") {
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
}
} }
} }
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -1,3 +0,0 @@
dokka_version=1.4.0
org.gradle.jvmargs=-Xmx1024m

View File

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

View File

@@ -10,17 +10,21 @@ allprojects {
} }
internalProject = { name -> internalProject = { name ->
if (releaseMode) { // if (releaseMode) {
"$group:$name:$version" // "$group:$name:$version"
} else { // } else {
// projectByName("$name")
// }
projectByName("$name") projectByName("$name")
} }
}
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"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings"
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"

View File

@@ -3,20 +3,42 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.4.10 kotlin_version=1.4.20
kotlin_coroutines_version=1.4.0 kotlin_coroutines_version=1.4.2
kotlin_serialisation_core_version=1.0.1 kotlin_serialisation_core_version=1.0.1
kotlin_exposed_version=0.28.1 kotlin_exposed_version=0.28.1
ktor_version=1.4.1 ktor_version=1.4.3
klockVersion=1.12.1 klockVersion=2.0.0
gradle_bintray_plugin_version=1.8.5
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12
uuidVersion=0.2.2 uuidVersion=0.2.3
# ANDROID
core_ktx_version=1.3.2
androidx_recycler_version=1.1.0
appcompat_version=1.2.0
android_minSdkVersion=19
android_compileSdkVersion=30
android_buildToolsVersion=30.0.2
dexcount_version=2.0.0
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# Dokka
dokka_version=1.4.10.2
# Project data
group=dev.inmo group=dev.inmo
version=0.2.5 version=0.4.9
android_code_version=13

View File

@@ -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.6.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

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

View File

@@ -64,10 +64,11 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true }, crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T> deserializer: DeserializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = createStandardWebsocketFlow( ) = createStandardWebsocketFlow(
url, url,
checkReconnection checkReconnection
) { ) {
standardKtorSerialFormat.decodeDefault(deserializer, it) serialFormat.decodeDefault(deserializer, it)
} }

View File

@@ -4,32 +4,61 @@ import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.post import io.ktor.client.request.post
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.*
import kotlinx.serialization.SerializationStrategy
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T> typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester(
private val client: HttpClient = HttpClient(),
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) {
suspend fun <ResultType> uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>,
value: T
) = serialFormat.encodeHex(
serializationStrategy,
value
)
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> createStandardWebsocketFlow(
url: String,
checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
}
val defaultRequester = UnifiedRequester()
suspend fun <ResultType> HttpClient.uniget( suspend fun <ResultType> HttpClient.uniget(
url: String, url: String,
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>
) = get<StandardKtorSerialInputData>( ) = defaultRequester.uniget(url, resultDeserializer)
url
).let {
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = standardKtorSerialFormat.encodeHex( fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value)
this,
value
)
suspend fun <BodyType, ResultType> HttpClient.unipost( suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String, url: String,
bodyInfo: BodyPair<BodyType>, bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>
) = post<StandardKtorSerialInputData>(url) { ) = defaultRequester.unipost(url, bodyInfo, resultDeserializer)
body = standardKtorSerialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
}

View File

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

View File

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

View File

@@ -3,16 +3,28 @@ package dev.inmo.micro_utils.ktor.common
typealias QueryParam = Pair<String, String?> typealias QueryParam = Pair<String, String?>
typealias QueryParams = Map<String, String?> typealias QueryParams = Map<String, String?>
/**
* Create query part which includes key=value pairs separated with &
*/
val QueryParams.asUrlQuery: String val QueryParams.asUrlQuery: String
get() = keys.joinToString("&") { "${it}${get(it) ?.let { value -> "=$value" }}" } get() = keys.joinToString("&") { "${it}${get(it) ?.let { value -> "=$value" }}" }
/**
* Create query part which includes key=value pairs separated with &
*/
val List<QueryParam>.asUrlQuery: String val List<QueryParam>.asUrlQuery: String
get() = joinToString("&") { (key, value) -> "${key}${value ?.let { _ -> "=$value" }}" } get() = joinToString("&") { (key, value) -> "${key}${value ?.let { _ -> "=$value" }}" }
/**
* Create query part which includes key=value pairs separated with & and attach to receiver
*/
fun String.includeQueryParams( fun String.includeQueryParams(
queryParams: QueryParams queryParams: QueryParams
): String = "$this${if(queryParams.isNotEmpty()) "${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" else ""}" ): String = "$this${if(queryParams.isNotEmpty()) "${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" else ""}"
/**
* Create query part which includes key=value pairs separated with & and attach to receiver
*/
fun String.includeQueryParams( fun String.includeQueryParams(
queryParams: List<QueryParam> queryParams: List<QueryParam>
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" ): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"

View File

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

View File

@@ -33,10 +33,11 @@ fun <T> Route.includeWebsocketHandling(
fun <T> Route.includeWebsocketHandling( fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
serializer: SerializationStrategy<T> serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = includeWebsocketHandling( ) = includeWebsocketHandling(
suburl, suburl,
flow flow
) { ) {
standardKtorSerialFormat.encodeDefault(serializer, it) serialFormat.encodeDefault(serializer, it)
} }

View File

@@ -3,12 +3,87 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.request.receive import io.ktor.request.receive
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.response.respondBytes import io.ktor.response.respondBytes
import kotlinx.serialization.DeserializationStrategy import io.ktor.routing.Route
import kotlinx.serialization.SerializationStrategy import io.ktor.util.pipeline.PipelineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.*
class UnifiedRouter(
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) {
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
call.respondBytes (
serialFormat.encodeDefault(answerSerializer, answer),
serialFormatContentType
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
serialFormat.decodeDefault(
deserializer,
call.receive()
)
}
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
field: String
) = call.parameters[field].also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
field: String
) = call.request.queryParameters[field]
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
serialFormat.decodeHex(
deserializer,
it
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
}
val defaultUnifiedRouter = UnifiedRouter()
suspend fun <T> ApplicationCall.unianswer( suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>, answerSerializer: SerializationStrategy<T>,

View File

@@ -1,60 +0,0 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}"
} else {
artifactId = "${project.name}-$name"
}
}
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
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"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
}
}
}
}
}

View File

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

View File

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

26
mppAndroidProject Normal file
View File

@@ -0,0 +1,26 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -9,6 +9,9 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
android {
publishAllLibraryVariants()
}
sourceSets { sourceSets {
commonMain { commonMain {
@@ -34,5 +37,14 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation "androidx.test.ext:junit:$test_ext_junit_version"
implementation "androidx.test.espresso:espresso-core:$espresso_core"
}
}
} }
} }
apply from: "$defaultAndroidSettingsPresetPath"

View File

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

View File

@@ -22,12 +22,27 @@ interface Pagination {
val size: Int val size: Int
} }
/**
* Logical shortcut for comparison that page is 0
*/
inline val Pagination.isFirstPage
get() = page == 0
/** /**
* First number in index of objects. It can be used as offset for databases or other data sources * First number in index of objects. It can be used as offset for databases or other data sources
*/ */
val Pagination.firstIndex: Int val Pagination.firstIndex: Int
get() = page * size get() = page * size
/**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
*
* [[firstIndex], [lastIndex]]; That means, that for [Pagination] with [Pagination.size] == 10 and [Pagination.page] == 1
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
*/
val Pagination.lastIndexExclusive: Int
get() = firstIndex + size
/** /**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range: * Last number in index of objects. In fact, one [Pagination] object represent data in next range:
* *
@@ -35,7 +50,7 @@ val Pagination.firstIndex: Int
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. * you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
*/ */
val Pagination.lastIndex: Int val Pagination.lastIndex: Int
get() = firstIndex + size - 1 get() = lastIndexExclusive - 1
/** /**
* Calculates pages count for given [datasetSize] * Calculates pages count for given [datasetSize]

View File

@@ -21,7 +21,12 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
} }
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
return subList(with.firstIndex, with.lastIndex + 1).createPaginationResult( val firstIndex = maxOf(with.firstIndex, 0)
val lastIndex = minOf(with.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
}
return subList(firstIndex, lastIndex).createPaginationResult(
with, with,
size.toLong() size.toLong()
) )

View File

@@ -0,0 +1,41 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
class PaginatedIterator<T>(
pageSize: Int,
private val countGetter: () -> Long,
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
) : Iterator<T> {
private var pagination = FirstPagePagination(pageSize)
private val currentStack = mutableListOf<T>()
override fun hasNext(): Boolean = currentStack.isNotEmpty() || (countGetter() < pagination.lastIndexExclusive)
override fun next(): T {
if (currentStack.isEmpty()) {
val resultPagination = paginationResultGetter.invoke(pagination)
currentStack.addAll(resultPagination.results)
require(currentStack.isNotEmpty()) { "There is no elements left" }
pagination = resultPagination.nextPage()
}
return currentStack.removeFirst()
}
}
class PaginatedIterable<T>(
private val pageSize: Int,
private val countGetter: () -> Long,
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
) : Iterable<T> {
override fun iterator(): Iterator<T> = PaginatedIterator(pageSize, countGetter, paginationResultGetter)
}
/**
* Will make iterable using incoming [countGetter] and [paginationResultGetter]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> makeIterable(
noinline countGetter: () -> Long,
pageSize: Int = defaultMediumPageSize,
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
{"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"}]},"type":"Multiplatform"} {"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,59 +1,76 @@
apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish'
apply from: "$publishMavenPath" task javadocsJar(type: Jar) {
classifier = 'javadoc'
bintray { }
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER') task sourceJar (type : Jar) {
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY') classifier = 'sources'
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "MicroUtils"
name = "${project.name}"
vcsUrl = "https://github.com/InsanusMokrassar/MicroUtils"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
}
}
}
} }
bintrayUpload.doFirst { afterEvaluate {
publications = publishing.publications.collect { project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) { if (it.name.contains('kotlinMultiplatform')) {
null artifactId = "${project.name}"
artifact sourceJar
} else { } else {
it.name artifactId = "${project.name}-$name"
}
} }
} - null
} }
bintrayUpload.dependsOn publishToMavenLocal publishing {
publications.all {
artifact javadocsJar
pom {
description = ""
name = "${project.name}"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
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"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"
}
}
}
repositories {
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")
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')
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
} }
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"
@@ -15,5 +16,18 @@ kotlin {
api "com.benasher44:uuid:$uuidVersion" api "com.benasher44:uuid:$uuidVersion"
} }
} }
jvmMain {
dependencies {
api internalProject("micro_utils.common")
}
}
androidMain {
dependencies {
api "androidx.core:core-ktx:$core_ktx_version"
api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines")
}
}
} }
} }

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.repos
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
suspend fun FromKey.toOutKey() = this as ToKey
suspend fun FromValue.toOutValue() = this as ToValue
suspend fun ToKey.toInnerKey() = this as FromKey
suspend fun ToValue.toInnerValue() = this as FromValue
}
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
}

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo { interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun contains(k: Key): Boolean suspend fun contains(k: Key): Boolean
suspend fun contains(k: Key, v: Value): Boolean suspend fun contains(k: Key, v: Value): Boolean
suspend fun count(k: Key): Long suspend fun count(k: Key): Long
@@ -35,59 +36,75 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
} }
} }
} }
@Deprecated("Renamed", ReplaceWith("ReadOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo")) typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
typealias OneToManyReadKeyValueRepo<Key, Value> = ReadOneToManyKeyValueRepo<Key, Value>
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo { interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>> val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Pair<Key, Value>> val onValueRemoved: Flow<Pair<Key, Value>>
val onDataCleared: Flow<Key> val onDataCleared: Flow<Key>
suspend fun add(toAdd: Map<Key, List<Value>>) = toAdd.forEach { (k, values) -> suspend fun add(toAdd: Map<Key, List<Value>>)
values.forEach { v ->
add(k, v)
}
}
@Deprecated("Will be extracted as extension for other add method")
suspend fun add(k: Key, v: Value)
suspend fun remove(toRemove: Map<Key, List<Value>>) = toRemove.forEach { (k, values) -> suspend fun remove(toRemove: Map<Key, List<Value>>)
values.forEach { v ->
remove(k, v)
}
}
@Deprecated("Will be extracted as extension for other remove method")
suspend fun remove(k: Key, v: Value)
suspend fun clear(k: Key) suspend fun clear(k: Key)
suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.keys.forEach { key -> clear(key) }
add(toSet)
}
} }
@Deprecated("Renamed", ReplaceWith("WriteOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo")) typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
typealias OneToManyWriteKeyValueRepo<Key, Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, v: List<Value>
) = add(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, vararg v: Value
) = add(k, v.toList())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, v: List<Value>
) = set(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value
) = set(k, v.toList())
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
vararg v: Value
) = add(mapOf(k to v.toList()))
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.remove(
k: Key,
vararg v: Value
) = remove(mapOf(k to v.toList()))
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.remove(
keysAndValues: List<Pair<Key, List<Value>>> keysAndValues: List<Pair<Key, List<Value>>>
) = remove(keysAndValues.toMap()) ) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
vararg keysAndValues: Pair<Key, List<Value>> vararg keysAndValues: Pair<Key, List<Value>>
) = remove(keysAndValues.toMap()) ) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
v: List<Value>
) = remove(mapOf(k to v))
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
vararg v: Value
) = remove(k, v.toList())

View File

@@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
suspend fun contains(id: IdType): Boolean suspend fun contains(id: IdType): Boolean
suspend fun count(): Long suspend fun count(): Long
} }
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType> typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
val <IdType> UpdatedValuePair<IdType, *>.id val <IdType> UpdatedValuePair<IdType, *>.id
@@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
suspend fun deleteById(ids: List<IdType>) suspend fun deleteById(ids: List<IdType>)
} }
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType vararg values: InputValueType
@@ -40,3 +42,4 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>, interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -8,16 +8,32 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key): Value? suspend fun get(k: Key): Value?
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun contains(key: Key): Boolean suspend fun contains(key: Key): Boolean
suspend fun count(): Long suspend fun count(): Long
} }
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
interface WriteStandardKeyValueRepo<Key, Value> : Repo { interface WriteStandardKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>> val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Key> val onValueRemoved: Flow<Key>
suspend fun set(k: Key, v: Value) suspend fun set(toSet: Map<Key, Value>)
suspend fun unset(k: Key) suspend fun unset(toUnset: List<Key>)
} }
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
k: Key, v: Value
) = set(k to v)
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>

View File

@@ -0,0 +1,146 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(k: FromKey): FromValue? = to.get(
k.toOutKey()
) ?.toInnerValue()
override suspend fun values(
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromValue> = to.values(
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun keys(
v: FromValue,
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
v.toOutValue(),
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun contains(key: FromKey): Boolean = to.contains(
key.toOutKey()
)
override suspend fun count(): Long = to.count()
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
override val onValueRemoved: Flow<FromKey> = to.onValueRemoved.map { k ->
k.toInnerKey()
}
override suspend fun set(toSet: Map<FromKey, FromValue>) = to.set(
toSet.map { (k, v) ->
k.toOutKey() to v.toOutValue()
}.toMap()
)
override suspend fun unset(toUnset: List<FromKey>) = to.unset(
toUnset.map { k ->
k.toOutKey()
}
)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: StandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : StandardKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)

View File

@@ -0,0 +1,155 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(
k: FromKey,
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromValue> = to.get(
k.toOutKey(),
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun keys(
v: FromValue,
pagination: Pagination,
reversed: Boolean
): PaginationResult<FromKey> = to.keys(
v.toOutValue(),
pagination,
reversed
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
override suspend fun contains(k: FromKey): Boolean = to.contains(k.toOutKey())
override suspend fun contains(k: FromKey, v: FromValue): Boolean = to.contains(k.toOutKey(), v.toOutValue())
override suspend fun count(): Long = to.count()
override suspend fun count(k: FromKey): Long = to.count(k.toOutKey())
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
override val onValueRemoved: Flow<Pair<FromKey, FromValue>> = to.onValueRemoved.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
override val onDataCleared: Flow<FromKey> = to.onDataCleared.map { k ->
k.toInnerKey()
}
override suspend fun add(toAdd: Map<FromKey, List<FromValue>>) = to.add(
toAdd.map { (k, v) ->
k.toOutKey() to v.map { it.toOutValue() }
}.toMap()
)
override suspend fun remove(toRemove: Map<FromKey, List<FromValue>>) = to.remove(
toRemove.map { (k, v) ->
k.toOutKey() to v.map { it.toOutValue() }
}.toMap()
)
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
to.set(
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()
)
}
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : OneToManyKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.set
class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: StandardKeyValueRepo<String, Int>,
override val database: T
) : StandardVersionsRepoProxy<T> {
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
override suspend fun updateTableVersion(tableName: String, version: Int) { keyValueStore.set(tableName, version) }
}

View File

@@ -0,0 +1,36 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
interface StandardVersionsRepoProxy<T> : Repo {
val database: T
suspend fun getTableVersion(tableName: String): Int?
suspend fun updateTableVersion(tableName: String, version: Int)
}
class StandardVersionsRepo<T>(
private val proxy: StandardVersionsRepoProxy<T>
) : VersionsRepo<T> {
override suspend fun setTableVersion(
tableName: String,
version: Int,
onCreate: suspend T.() -> Unit,
onUpdate: suspend T.(from: Int, to: Int) -> Unit
) {
var savedVersion = proxy.getTableVersion(tableName)
if (savedVersion == null) {
proxy.database.onCreate()
proxy.updateTableVersion(tableName, version)
} else {
while (savedVersion != null && savedVersion < version) {
val newVersion = savedVersion + 1
proxy.database.onUpdate(savedVersion, newVersion)
proxy.updateTableVersion(tableName, newVersion)
savedVersion = newVersion
}
}
}
}

View File

@@ -0,0 +1,31 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.Repo
/**
* This interface has been created due to requirement to work with different versions of databases and make some
* migrations between versions
*
* @param T It is a type of database, which will be used by this repo to retrieve current table version and update it
*/
interface VersionsRepo<T> : Repo {
/**
* By default, instance of this interface will check that version of table with name [tableName] is less than
* [version] or is absent
*
* * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
* * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
* until database version will be equal to [version]
*
* @param version Current version of table
* @param onCreate This callback will be called in case when table have no information about table
* @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter
* "to" will always be greater than "from"
*/
suspend fun setTableVersion(
tableName: String,
version: Int,
onCreate: suspend T.() -> Unit = {},
onUpdate: suspend T.(from: Int, to: Int) -> Unit = { _, _ ->}
)
}

View File

@@ -0,0 +1,171 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.reverse
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds.*
private inline val String.isAbsolute
get() = startsWith(File.separator)
class FileReadStandardKeyValueRepo(
private val folder: File
) : ReadStandardKeyValueRepo<String, File> {
init {
folder.mkdirs()
}
override suspend fun get(k: String): File? {
val file = File(folder, k)
if (file.exists()) {
return file
}
return null
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
return filesPaths.map { File(folder, it) }.createPaginationResult(
resultPagination,
count
)
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
return filesPaths.toList().createPaginationResult(
resultPagination,
count
)
}
override suspend fun keys(
v: File,
pagination: Pagination,
reversed: Boolean
): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(1L) else pagination
return if (resultPagination.isFirstPage) {
val fileSubpath = v.absolutePath.removePrefix(folder.absolutePath)
if (fileSubpath == v.absolutePath) {
emptyList()
} else {
listOf(fileSubpath)
}
} else {
emptyList()
}.createPaginationResult(resultPagination, 1L)
}
override suspend fun contains(key: String): Boolean {
return File(folder, key).exists()
}
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
}
/**
* Files watching will not correctly works on Android with version of API lower than API 26
*/
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
class FileWriteStandardKeyValueRepo(
private val folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : WriteStandardKeyValueRepo<String, File> {
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
private val _onValueRemoved = MutableSharedFlow<String>()
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
init {
folder.mkdirs()
filesChangedProcessingScope ?.let {
it.launch {
try {
val folderPath = folder.toPath()
while (isActive) {
val key = try {
folderPath.register(FileSystems.getDefault().newWatchService(), ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
} catch (e: Exception) {
// add verbose way to show that file watching is not working
return@launch
}
for (event in key.pollEvents()) {
val relativeFilePath = (event.context() as? Path) ?: continue
val file = relativeFilePath.toFile()
val relativePath = file.toRelativeString(folder)
when (event.kind()) {
ENTRY_CREATE, ENTRY_MODIFY -> {
launch {
_onNewValue.emit(relativePath to file)
}
}
ENTRY_DELETE -> {
launch {
_onValueRemoved.emit(relativePath)
}
}
}
}
if (key.isValid || folder.exists()) {
continue
}
break
}
} catch (e: Throwable) {
// add verbose way to notify that this functionality is disabled
}
}
}
}
override suspend fun set(toSet: Map<String, File>) {
supervisorScope {
toSet.map { (filename, fileSource) ->
launch {
val file = File(folder, filename)
file.delete()
fileSource.copyTo(file, overwrite = true)
_onNewValue.emit(filename to file)
}
}
}.joinAll()
}
override suspend fun unset(toUnset: List<String>) {
toUnset.forEach {
val file = File(folder, it)
if (file.exists()) {
file.delete()
_onValueRemoved.emit(it)
}
}
}
}
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
class FileStandardKeyValueRepo(
folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : StandardKeyValueRepo<String, File>,
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)

View File

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

View File

@@ -0,0 +1,46 @@
package dev.inmo.micro_utils.repos
sealed class ColumnType(
typeName: String,
nullable: Boolean
) {
open val asType: String = "$typeName${if (!nullable) " not null" else ""}"
sealed class Text(
nullable: Boolean
) : ColumnType("text", nullable) {
object NULLABLE : Text(true)
object NOT_NULLABLE : Text(false)
}
sealed class Numeric(
typeName: String,
autoincrement: Boolean = false,
primaryKey: Boolean = false,
nullable: Boolean = false
) : ColumnType(
typeName,
nullable
) {
override val asType: String = "${super.asType}${if (primaryKey) " primary key" else ""}${if (autoincrement) " autoincrement" else ""}"
class INTEGER(
autoincrement: Boolean = false,
primaryKey: Boolean = false,
nullable: Boolean = false
) : Numeric(
"integer",
autoincrement,
primaryKey,
nullable
)
class DOUBLE(autoincrement: Boolean = false, primaryKey: Boolean = false, nullable: Boolean = false) : Numeric(
"double",
autoincrement,
primaryKey,
nullable
)
}
override fun toString(): String {
return asType
}
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.repos
import androidx.core.content.contentValuesOf
@Suppress("UNCHECKED_CAST", "SimplifiableCall")
fun contentValuesOfNotNull(vararg pairs: Pair<String, Any?>?) = contentValuesOf(
*(pairs.filter { it != null } as List<Pair<String, Any?>>).toTypedArray()
)

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.repos
import android.database.Cursor
inline fun <T> Cursor.map(
block: (Cursor) -> T
): List<T> {
val result = mutableListOf<T>()
if (moveToFirst()) {
do {
result.add(block(this))
} while (moveToNext())
}
return result
}
fun Cursor.firstOrNull(): Cursor? = if (moveToFirst()) {
this
} else {
null
}

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.repos
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
val DatabaseCoroutineContext: CoroutineContext = Dispatchers.IO

View File

@@ -0,0 +1,64 @@
package dev.inmo.micro_utils.repos
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
fun createTableQuery(
tableName: String,
vararg columnsToTypes: Pair<String, ColumnType>
) = "create table $tableName (${columnsToTypes.joinToString(", ") { "${it.first} ${it.second}" }});"
fun SQLiteDatabase.createTable(
tableName: String,
vararg columnsToTypes: Pair<String, ColumnType>,
onInit: (SQLiteDatabase.() -> Unit)? = null
): Boolean {
val existing = rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'", null).use {
it.count > 0
}
return if (existing) {
false
// TODO:: add upgrade opportunity
} else {
execSQL(createTableQuery(tableName, *columnsToTypes))
onInit ?.invoke(this)
true
}
}
fun Cursor.getString(columnName: String) = getString(
getColumnIndex(columnName)
)
fun Cursor.getLong(columnName: String) = getLong(
getColumnIndex(columnName)
)
fun Cursor.getInt(columnName: String) = getInt(
getColumnIndex(columnName)
)
fun Cursor.getDouble(columnName: String) = getDouble(
getColumnIndex(columnName)
)
fun SQLiteDatabase.select(
table: String,
columns: Array<String>? = null,
selection: String? = null,
selectionArgs: Array<String>? = null,
groupBy: String? = null,
having: String? = null,
orderBy: String? = null,
limit: String? = null
) = query(
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
)
fun makePlaceholders(count: Int): String {
return (0 until count).joinToString { "?" }
}
fun makeStringPlaceholders(count: Int): String {
return (0 until count).joinToString { "\"?\"" }
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.firstIndex
fun limitClause(size: Long, since: Long? = null) = "${since ?.let { "$it, " } ?: ""}$size"
fun limitClause(size: Int, since: Int? = null) = limitClause(size.toLong(), since ?.toLong())
fun Pagination.limitClause() = limitClause(size, firstIndex)

View File

@@ -0,0 +1,87 @@
package dev.inmo.micro_utils.repos
import android.database.sqlite.SQLiteDatabase
import dev.inmo.micro_utils.coroutines.safely
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.Executors
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
private object ContextsPool {
private val contexts = mutableListOf<CoroutineContext>()
private val mutex = Mutex(locked = false)
private val freeContexts = mutableListOf<CoroutineContext>()
suspend fun acquireContext(): CoroutineContext {
return mutex.withLock {
freeContexts.removeFirstOrNull() ?: Executors.newSingleThreadExecutor().asCoroutineDispatcher().also {
contexts.add(it)
}
}
}
suspend fun freeContext(context: CoroutineContext) {
return mutex.withLock {
if (context in contexts && context !in freeContexts) {
freeContexts.add(context)
}
}
}
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
try {
block(it)
} finally {
freeContext(it)
}
}
}
class TransactionContext(
val databaseContext: CoroutineContext
): CoroutineContext.Element {
override val key: CoroutineContext.Key<TransactionContext> = TransactionContext
companion object : CoroutineContext.Key<TransactionContext>
}
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
return coroutineContext[TransactionContext] ?.let {
withContext(it.databaseContext) {
block()
}
} ?: ContextsPool.use { context ->
withContext(TransactionContext(context) + context) {
beginTransaction()
safely(
{
endTransaction()
throw it
}
) {
block().also {
setTransactionSuccessful()
endTransaction()
}
}
}
}
}
inline fun <T> SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T {
return when {
inTransaction() -> block()
else -> {
beginTransaction()
try {
block().also {
setTransactionSuccessful()
}
} finally {
endTransaction()
}
}
}
}

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.repos
val internalId = "_id"
val internalIdType = ColumnType.Numeric.INTEGER(
autoincrement = true,
primaryKey = true
)

View File

@@ -0,0 +1,55 @@
package dev.inmo.micro_utils.repos
import android.content.Context
import android.database.DatabaseErrorHandler
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.repos.versions.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
private data class CallbackContinuationPair<T> (
val callback: suspend SQLiteDatabase.() -> T,
val continuation: Continuation<T>
) {
suspend fun SQLiteDatabase.execute() {
safely(
{
continuation.resumeWithException(it)
}
) {
continuation.resume(callback())
}
}
}
class StandardSQLHelper(
context: Context,
name: String,
factory: SQLiteDatabase.CursorFactory? = null,
version: Int = 1,
errorHandler: DatabaseErrorHandler? = null
) {
val sqlOpenHelper = object : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
override fun onCreate(db: SQLiteDatabase?) {}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}
}
val versionsRepo: VersionsRepo<SQLiteOpenHelper> by lazy {
StandardVersionsRepo(AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper))
}
suspend fun <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
}
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
return writableDatabase.transaction(block)
}
suspend fun <T> SQLiteOpenHelper.readableTransaction(block: suspend SQLiteDatabase.() -> T): T {
return readableDatabase.transaction(block)
}

View File

@@ -0,0 +1,67 @@
package dev.inmo.micro_utils.repos.crud
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.createPaginationResult
import dev.inmo.micro_utils.repos.*
val <T> T.asId: String
get() = (this as? String) ?: this!!.toString()
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
protected val helper: StandardSQLHelper
) : ReadStandardCRUDRepo<ObjectType, IdType> {
protected abstract val tableName: String
protected abstract val idColumnName: String
protected abstract suspend fun Cursor.toObject(): ObjectType
protected fun SQLiteDatabase.count(): Long = select(tableName).use {
it.count
}.toLong()
override suspend fun contains(id: IdType): Boolean = helper.readableTransaction {
select(
tableName,
null,
"$idColumnName=?",
arrayOf(id.asId)
).use {
it.count > 0
}
}
override suspend fun getById(id: IdType): ObjectType? = helper.readableTransaction {
select(
tableName,
selection = "$idColumnName=?",
selectionArgs = arrayOf(id.asId),
limit = limitClause(1)
).use { c ->
if (c.moveToFirst()) {
c.toObject()
} else {
null
}
}
}
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
return helper.readableTransaction {
select(
tableName,
limit = pagination.limitClause()
).use {
if (it.moveToFirst()) {
val resultList = mutableListOf(it.toObject())
while (it.moveToNext()) {
resultList.add(it.toObject())
}
resultList.createPaginationResult(pagination, count())
} else {
emptyList<ObjectType>().createPaginationResult(pagination, 0)
}
}
}
}
}

View File

@@ -0,0 +1,102 @@
package dev.inmo.micro_utils.repos.crud
import android.content.ContentValues
import dev.inmo.micro_utils.common.mapNotNullA
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.*
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
helper: StandardSQLHelper
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
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())
}
}
return helper.readableTransaction {
indexes.mapNotNullA { i ->
select(
tableName,
selection = "$internalId=?",
selectionArgs = arrayOf(i.toString())
).use { c ->
if (c.moveToFirst()) {
c.toObject()
} else {
null
}
}
}
}.also {
it.forEach {
newObjectsChannel.emit(it)
}
}
}
override suspend fun deleteById(ids: List<IdType>) {
val deleted = mutableListOf<IdType>()
helper.writableTransaction {
ids.forEach { id ->
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
if (it > 0) {
deleted.add(id)
}
}
}
}
deleted.forEach {
deleteObjectsIdsChannel.emit(it)
}
}
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
val asContentValues = value.asContentValues(id)
if (asContentValues.keySet().isNotEmpty()) {
helper.writableTransaction {
update(
tableName,
asContentValues,
"$idColumnName=?",
arrayOf(id.asId)
)
}
}
return getById(id) ?.also {
updateObjectsChannel.emit(it)
}
}
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
helper.writableTransaction {
values.forEach { (id, value) ->
update(
tableName,
value.asContentValues(id),
"$idColumnName=?",
arrayOf(id.asId)
)
}
}
return values.mapNotNullA {
getById(it.first)
}.also {
it.forEach {
updateObjectsChannel.emit(it)
}
}
}
override suspend fun count(): Long = helper.readableTransaction { select(tableName).use { it.count.toLong() } }
}

View File

@@ -0,0 +1,141 @@
package dev.inmo.micro_utils.repos.keyvalue
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
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.StandardKeyValueRepo
import kotlinx.coroutines.flow.*
private val cache = HashMap<String, KeyValueStore<*>>()
fun <T : Any> Context.keyValueStore(
name: String = "default",
cacheValues: Boolean = false
): StandardKeyValueRepo<String, T> {
return cache.getOrPut(name) {
KeyValueStore<T>(this, name, cacheValues)
} as KeyValueStore<T>
}
class KeyValueStore<T : Any> internal constructor (
c: Context,
preferencesName: String,
useCache: Boolean = false
) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<String, T> {
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
private val cachedData = if (useCache) {
mutableMapOf<String, Any>()
} else {
null
}
private val onNewValueChannel = MutableSharedFlow<Pair<String, T>>()
private val onValueRemovedChannel = MutableSharedFlow<String>()
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asSharedFlow()
override val onValueRemoved: Flow<String> = onValueRemovedChannel.asSharedFlow()
init {
cachedData ?.let {
sharedPreferences.all.forEach {
if (it.value != null) {
cachedData[it.key] = it.value as Any
}
}
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
}
override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String) {
val value = sp.all[key]
cachedData ?: return
if (value != null) {
cachedData[key] = value
} else {
cachedData.remove(key)
}
}
override suspend fun get(k: String): T? {
return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<T> {
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
return sharedPreferences.all.values.paginate(
resultPagination
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it as T }.let { if (reversed) it.reversed() else it },
it.size
)
}
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
return sharedPreferences.all.keys.paginate(
resultPagination
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.let { if (reversed) it.reversed() else it },
it.size
)
}
}
override suspend fun keys(v: T, pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val resultPagination = if (reversed) pagination.reverse(count()) else pagination
return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate(
resultPagination
).let {
PaginationResult(
it.page,
it.pagesNumber,
it.results.let { if (reversed) it.reversed() else it },
it.size
)
}
}
override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key)
override suspend fun count(): Long = sharedPreferences.all.size.toLong()
override suspend fun set(toSet: Map<String, T>) {
sharedPreferences.edit {
toSet.forEach { (k, v) ->
when(v) {
is Int -> putInt(k, v)
is Long -> putLong(k, v)
is Float -> putFloat(k, v)
is String -> putString(k, v)
is Boolean -> putBoolean(k, v)
is Set<*> -> putStringSet(k, v.map { (it as? String) ?: it.toString() }.toSet())
else -> error(
"Currently supported only primitive types and set for SharedPreferences KeyValue repos"
)
}
}
}
toSet.forEach { (k, v) ->
onNewValueChannel.emit(k to v)
}
}
override suspend fun unset(toUnset: List<String>) {
sharedPreferences.edit {
toUnset.forEach { remove(it) }
}
toUnset.forEach { onValueRemovedChannel.emit(it) }
}
}

View File

@@ -0,0 +1,237 @@
package dev.inmo.micro_utils.repos.onetomany
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.common.mapNotNullA
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.createPaginationResult
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
private val internalSerialFormat = Json {
ignoreUnknownKeys = true
}
class OneToManyAndroidRepo<Key, Value>(
private val tableName: String,
private val keySerializer: KSerializer<Key>,
private val valueSerializer: KSerializer<Value>,
private val helper: SQLiteOpenHelper
) : OneToManyKeyValueRepo<Key, Value> {
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
private val _onDataCleared = MutableSharedFlow<Key>()
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
private val idColumnName = "id"
private val valueColumnName = "value"
private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this)
private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this)
private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this)
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
init {
runBlocking(DatabaseCoroutineContext) {
helper.writableTransaction {
createTable(
tableName,
internalId to internalIdType,
idColumnName to ColumnType.Text.NOT_NULLABLE,
valueColumnName to ColumnType.Text.NULLABLE
)
}
}
}
override suspend fun add(toAdd: Map<Key, List<Value>>) {
val added = mutableListOf<Pair<Key, Value>>()
helper.writableTransaction {
toAdd.forEach { (k, values) ->
values.forEach { v ->
insert(
tableName,
null,
contentValuesOf(
idColumnName to k.asId(),
valueColumnName to v.asValue()
)
).also {
if (it != -1L) {
added.add(k to v)
}
}
}
}
}
added.forEach { _onNewValue.emit(it) }
}
override suspend fun clear(k: Key) {
helper.writableTransaction {
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
}.also {
if (it > 0) {
_onDataCleared.emit(k)
}
}
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
val (clearedKeys, inserted) = helper.writableTransaction {
toSet.mapNotNull { (k, _) ->
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
k
} else {
null
}
} to toSet.flatMap { (k, values) ->
values.map { v ->
insert(
tableName,
null,
contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue())
)
k to v
}
}
}
clearedKeys.forEach { _onDataCleared.emit(it) }
inserted.forEach { newPair -> _onNewValue.emit(newPair) }
}
override suspend fun contains(k: Key): Boolean = helper.readableTransaction {
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 {
select(
tableName,
selection = "$idColumnName=? AND $valueColumnName=?",
selectionArgs = arrayOf(k.asId(), v.asValue()),
limit = FirstPagePagination(1).limitClause()
).use {
it.count > 0
}
}
override suspend fun count(): Long =helper.readableTransaction {
select(
tableName
).use {
it.count
}
}.toLong()
override suspend fun count(k: Key): Long = helper.readableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
it.count
}
}.toLong()
override suspend fun get(
k: Key,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Value> = count(k).let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction {
select(
tableName,
selection = "$idColumnName=?",
selectionArgs = arrayOf(k.asId()),
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Value>().also {
if (c.moveToFirst()) {
do {
it.add(c.getString(valueColumnName).asValue())
} while (c.moveToNext())
}
}
}
}.createPaginationResult(
pagination,
count
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction {
select(
tableName,
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Key>().also {
if (c.moveToFirst()) {
do {
it.add(c.getString(idColumnName).asKey())
} while (c.moveToNext())
}
}
}
}.createPaginationResult(
pagination,
count
)
}
override suspend fun keys(
v: Value,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction {
select(
tableName,
selection = "$valueColumnName=?",
selectionArgs = arrayOf(v.asValue()),
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Key>().also {
if (c.moveToFirst()) {
do {
it.add(c.getString(idColumnName).asKey())
} while (c.moveToNext())
}
}
}
}.createPaginationResult(
pagination,
count
)
}
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
helper.writableTransaction {
toRemove.flatMap { (k, vs) ->
vs.mapNotNullA { v ->
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
k to v
} else {
null
}
}
}
}.forEach { (k, v) ->
_onValueRemoved.emit(k to v)
}
}
}

View File

@@ -0,0 +1,63 @@
package dev.inmo.micro_utils.repos.versions
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.runBlocking
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper] with table inside of [database]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun versionsRepo(database: SQLiteOpenHelper): VersionsRepo<SQLiteOpenHelper> = StandardVersionsRepo(
AndroidSQLStandardVersionsRepoProxy(database)
)
class AndroidSQLStandardVersionsRepoProxy(
override val database: SQLiteOpenHelper
) : StandardVersionsRepoProxy<SQLiteOpenHelper> {
private val tableName: String = "AndroidSQLStandardVersionsRepo"
private val tableNameColumnName = "tableName"
private val tableVersionColumnName = "version"
init {
runBlocking(DatabaseCoroutineContext) {
database.writableTransaction {
createTable(
tableName,
tableNameColumnName to ColumnType.Text.NOT_NULLABLE,
tableVersionColumnName to ColumnType.Numeric.INTEGER()
)
}
}
}
override suspend fun getTableVersion(tableName: String): Int? = database.writableTransaction {
select(
this@AndroidSQLStandardVersionsRepoProxy.tableName,
selection = "$tableNameColumnName=?",
selectionArgs = arrayOf(tableName),
limit = limitClause(1)
).use {
if (it.moveToFirst()) {
it.getInt(tableVersionColumnName)
} else {
null
}
}
}
override suspend fun updateTableVersion(tableName: String, version: Int) {
database.writableTransaction {
val updated = update(
this@AndroidSQLStandardVersionsRepoProxy.tableName,
contentValuesOf(tableVersionColumnName to version),
"$tableNameColumnName=?",
arrayOf(tableName)
) > 0
if (!updated) {
insert(this@AndroidSQLStandardVersionsRepoProxy.tableName, null, contentValuesOf(tableNameColumnName to tableName, tableVersionColumnName to version))
}
}
}
}

View File

@@ -0,0 +1,49 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.repos.versions
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
import kotlinx.coroutines.runBlocking
/**
* Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo]
*
* @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [KeyValueBasedVersionsRepoProxy]
* @see [keyValueStore]
*/
inline fun <T> Context.versionsKeyValueRepo(
database: T
): VersionsRepo<T> = StandardVersionsRepo(
KeyValueBasedVersionsRepoProxy(
keyValueStore("SPVersionsRepo"),
database
)
)
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
*
* @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [keyValueStore]
*/
inline fun Context.versionsKeyValueRepoForSQL(
database: SQLiteOpenHelper
) = versionsKeyValueRepo(database)
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
*
* @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [keyValueStore]
*/
inline fun versionsRepo(
context: Context,
database: SQLiteOpenHelper
) = context.versionsKeyValueRepoForSQL(database)

View File

@@ -1,9 +1,7 @@
package dev.inmo.micro_utils.repos.exposed package dev.inmo.micro_utils.repos.exposed
import kotlinx.coroutines.channels.Channel
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = Channel.BUFFERED, flowsChannelsSize: Int = 0,
tableName: String = "" tableName: String = ""
) : ) :
AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(

View File

@@ -2,32 +2,32 @@ package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.UpdatedValuePair import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement import org.jetbrains.exposed.sql.statements.UpdateStatement
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 64, flowsChannelsSize: Int = 0,
tableName: String = "" tableName: String = ""
) : ) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName), AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
{ {
protected val newObjectsChannel = BroadcastChannel<ObjectType>(flowsChannelsSize) protected val newObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(flowsChannelsSize) protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(flowsChannelsSize) protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(flowsChannelsSize)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asFlow() override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asFlow() override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asFlow() override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
@Deprecated("Will be removed in near major update. Override open fun with the same name instead")
abstract val InsertStatement<Number>.asObject: ObjectType abstract val InsertStatement<Number>.asObject: ObjectType
abstract val selectByIds: SqlExpressionBuilder.(List<out IdType>) -> Op<Boolean> protected open fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType = asObject
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>) protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement) protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
@@ -35,7 +35,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {} protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
private fun createWithoutNotification(value: InputValueType): ObjectType { private fun createWithoutNotification(value: InputValueType): ObjectType {
return transaction(database) { return transaction(database) {
insert { insert(value, it) }.asObject insert { insert(value, it) }.asObject(value)
} }
} }
@@ -43,10 +43,8 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
onBeforeCreate(values) onBeforeCreate(values)
return transaction(db = database) { return transaction(db = database) {
values.map { value -> createWithoutNotification(value) } values.map { value -> createWithoutNotification(value) }
}.also { }.onEach {
it.forEach { newObjectsChannel.emit(it)
newObjectsChannel.send(it)
}
} }
} }
@@ -75,7 +73,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
onBeforeUpdate(listOf(id to value)) onBeforeUpdate(listOf(id to value))
return updateWithoutNotification(id, value).also { return updateWithoutNotification(id, value).also {
if (it != null) { if (it != null) {
updateObjectsChannel.send(it) updateObjectsChannel.emit(it)
} }
} }
} }
@@ -84,13 +82,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
return ( return (
transaction(db = database) { transaction(db = database) {
values.map { (id, value) -> updateWithoutNotification(id, value) } values.map { (id, value) -> updateWithoutNotification(id, value) }
}.filter { }.filterNotNull()
it != null ).onEach {
} as List<ObjectType> updateObjectsChannel.emit(it)
).also {
it.forEach {
updateObjectsChannel.send(it)
}
} }
} }
protected open suspend fun onBeforeDelete(ids: List<IdType>) {} protected open suspend fun onBeforeDelete(ids: List<IdType>) {}

View File

@@ -2,10 +2,9 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.StandardKeyValueRepo import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.channels.BroadcastChannel import dev.inmo.micro_utils.repos.exposed.initTable
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.asFlow
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@@ -20,35 +19,45 @@ open class ExposedKeyValueRepo<Key, Value>(
valueColumnAllocator, valueColumnAllocator,
tableName tableName
) { ) {
private val onNewValueChannel = BroadcastChannel<Pair<Key, Value>>(Channel.BUFFERED) private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
private val onValueRemovedChannel = BroadcastChannel<Key>(Channel.BUFFERED) private val _onValueRemoved = MutableSharedFlow<Key>()
override val onNewValue: Flow<Pair<Key, Value>> = onNewValueChannel.asFlow() override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = onValueRemovedChannel.asFlow() override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
override suspend fun set(k: Key, v: Value) { override suspend fun set(toSet: Map<Key, Value>) {
transaction(database) { transaction(database) {
if (select { keyColumn.eq(k) }.limit(1).any()) { toSet.mapNotNull { (k, v) ->
update({ keyColumn.eq(k) }) { if (update({ keyColumn.eq(k) }) { it[valueColumn] = v } > 0) {
it[valueColumn] = v k to v
}
} else { } else {
insert { val inserted = insert {
it[keyColumn] = k it[keyColumn] = k
it[valueColumn] = v it[valueColumn] = v
}.getOrNull(keyColumn) != null
if (inserted) {
k to v
} else {
null
} }
} }
} }
onNewValueChannel.send(k to v) }.forEach {
_onNewValue.emit(it)
}
} }
override suspend fun unset(k: Key) { override suspend fun unset(toUnset: List<Key>) {
transaction(database) { transaction(database) {
deleteWhere { keyColumn.eq(k) } toUnset.mapNotNull {
if (deleteWhere { keyColumn.eq(it) } > 0) {
it
} else {
null
}
}
}.forEach {
_onValueRemoved.emit(it)
} }
onValueRemovedChannel.send(k)
} }
} }
@Deprecated("Renamed", ReplaceWith("ExposedKeyValueRepo", "dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo"))
typealias AbstractExposedKeyValueRepo<Key, Value> = ExposedKeyValueRepo<Key, Value>

View File

@@ -2,8 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.ExposedRepo
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@@ -17,6 +16,8 @@ open class ExposedReadKeyValueRepo<Key, Value>(
protected val valueColumn: Column<Value> = valueColumnAllocator() protected val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn) override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
init { initTable() }
override suspend fun get(k: Key): Value? = transaction(database) { override suspend fun get(k: Key): Value? = transaction(database) {
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn) select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
} }
@@ -33,12 +34,19 @@ open class ExposedReadKeyValueRepo<Key, Value>(
} }
}.createPaginationResult(pagination, count()) }.createPaginationResult(pagination, count())
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
select { valueColumn.eq(v) }.let {
it.count() to it.paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
it[keyColumn]
}
}
}.let { (count, list) ->
list.createPaginationResult(pagination, count)
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) { override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map { selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
it[valueColumn] it[valueColumn]
} }
}.createPaginationResult(pagination, count()) }.createPaginationResult(pagination, count())
} }
@Deprecated("Renamed", ReplaceWith("ExposedReadKeyValueRepo", "dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo"))
typealias AbstractExposedReadKeyValueRepo<Key, Value> = ExposedReadKeyValueRepo<Key, Value>

Some files were not shown because too many files have changed in this diff Show More