Compare commits

...

78 Commits

Author SHA1 Message Date
eb959a3135 add regular build workflow 2021-06-11 22:33:08 +06:00
24033e0cac firstNotNull and LinkedSupervisor(Job|Scope) 2021-06-11 17:32:13 +06:00
71f9a505e0 start 0.5.8 2021-06-11 17:03:20 +06:00
979b8f017b Merge pull request #71 from InsanusMokrassar/0.5.7
0.5.7
2021-06-06 10:46:13 +06:00
af78f01682 remove progressbar alert dialog 2021-06-06 10:43:42 +06:00
0b16d5c826 upfix 2021-06-06 02:10:48 +06:00
597e14bc7e fix of alert dialog with progress bar background 2021-06-06 02:04:21 +06:00
04a95867e2 fixes 2021-06-06 01:46:33 +06:00
e0d5eb45b7 separate cache repos to read and read/write 2021-06-05 20:29:59 +06:00
b90cab318e start 0.5.7 2021-06-05 20:12:41 +06:00
3252b61abe Merge pull request #70 from InsanusMokrassar/0.5.6
0.5.6
2021-06-05 15:23:40 +06:00
2a2da21ff3 fillup safelyWithResult 2021-06-05 15:18:48 +06:00
04ef371337 update safelyWithResult 2021-06-05 15:18:09 +06:00
623e0cd369 improve launchSynchronously 2021-06-05 15:16:07 +06:00
1f466747f0 update exposed 2021-06-05 15:08:22 +06:00
2215462f99 start 0.5.6 2021-06-05 15:07:27 +06:00
ac4c0a2e4c Merge pull request #69 from InsanusMokrassar/0.5.5
0.5.5
2021-05-28 23:50:35 +06:00
f7496db5ac Update CHANGELOG.md 2021-05-28 19:34:30 +06:00
3028fe975d Update ktor 2021-05-28 19:33:02 +06:00
23a5034493 start 0.5.5 2021-05-28 19:32:04 +06:00
65e339f811 Merge pull request #68 from InsanusMokrassar/0.5.4
0.5.4
2021-05-26 22:33:53 +06:00
2020e48659 fixes of deprecates 2021-05-26 22:33:08 +06:00
9566d6f81f upfix of android version increasing 2021-05-26 22:22:43 +06:00
a00d734712 Merge pull request #67 from InsanusMokrassar/0.5.4
0.5.4
2021-05-26 22:07:28 +06:00
27a3e8706a update klock 2021-05-26 22:05:34 +06:00
e601efcfc0 Update gradle.properties 2021-05-26 22:03:30 +06:00
2bfad9f885 Merge pull request #66 from InsanusMokrassar/0.5.3
0.5.3
2021-05-26 18:06:39 +06:00
e78e984943 fillup changelog 2021-05-26 18:00:24 +06:00
242f4b02d0 return previous logic of work with safely 2021-05-26 17:59:45 +06:00
041be5a1d1 Update gradle.properties 2021-05-25 16:08:07 +06:00
976ce056c1 runCatching for safely and safelyWithoutExceptions 2021-05-25 00:18:46 +06:00
00c23c73a8 several small improvements 2021-05-24 20:26:48 +06:00
9dd1848337 note about safely logic 2021-05-24 12:31:32 +06:00
9b30efd9a2 optimize imports 2021-05-24 12:29:31 +06:00
5853f7cc49 coroutines updates 2021-05-24 12:27:54 +06:00
7b00a06f3e start 0.5.3 2021-05-23 00:03:43 +06:00
9ef9be0f37 Merge pull request #64 from InsanusMokrassar/0.5.2
0.5.2
2021-05-21 18:08:34 +06:00
13ca419473 fixes in UnifiedRequester 2021-05-21 18:04:04 +06:00
b80f1a0773 start 0.5.2 2021-05-21 17:56:36 +06:00
e85101c74e Merge pull request #63 from InsanusMokrassar/0.5.1
0.5.1
2021-05-20 10:23:40 +06:00
90668bdf63 Update CHANGELOG.md 2021-05-20 10:21:09 +06:00
e1a00079a5 Update gradle.properties 2021-05-20 10:11:53 +06:00
e3add4df42 Update CHANGELOG.md 2021-05-20 10:11:07 +06:00
ced1a3bccb start 0.5.1 2021-05-20 10:10:30 +06:00
8d13a14343 Merge pull request #62 from InsanusMokrassar/0.5.0
0.5.0
2021-05-18 09:42:15 +06:00
7238e1ea8a Update CHANGELOG.md 2021-05-18 09:41:59 +06:00
bd0423f243 Update CHANGELOG.md 2021-05-15 01:36:56 +06:00
b0441c134c Update gradle.properties 2021-05-15 01:36:26 +06:00
5860901c30 recyclerview alerts public 2021-05-12 21:16:05 +06:00
df4eaea4b9 doInDefault and doInIO 2021-05-10 19:24:21 +06:00
e9223d5502 add doSynchronously 2021-05-10 19:20:57 +06:00
702c5a3e5d update coroutines 2021-05-10 13:49:27 +06:00
ccbed95cdc update uuid and fill changelog 2021-05-06 18:18:42 +06:00
c4e2c06cf5 remove excluding signMetadataPublication 2021-05-06 18:02:33 +06:00
a6135738a3 fixes in build 2021-05-06 17:52:53 +06:00
ad1ea985b8 invalid update up to kotlin 1.5.0 2021-05-06 15:58:18 +06:00
2058950d07 update ktor 2021-05-03 10:48:31 +06:00
4dc27f4489 all deprecations were removed 2021-04-29 12:28:52 +06:00
122daa3220 update dependencies 2021-04-28 23:58:55 +06:00
e30928e23d start 0.5.0 2021-04-28 23:25:21 +06:00
30292306bb hotfix 2021-04-21 02:02:25 +06:00
2f9aa585f1 fix for repos in building 2021-04-21 01:52:17 +06:00
9e02c3e5ff rewrite launchSynchronously 2021-04-20 21:39:48 +06:00
7f813a519b one more update to versions 2021-04-20 20:37:23 +06:00
b5072486b4 hotfix for versions repo in android 2021-04-20 19:38:07 +06:00
50c1cd8215 fix of including 2021-04-20 19:17:59 +06:00
ed4812e6d8 Merge pull request #61 from InsanusMokrassar/0.4.36
0.4.36
2021-04-20 18:45:40 +06:00
59e0e751f1 all android submodules with common mpp template now incldes JVM code 2021-04-20 18:37:42 +06:00
a5ae5e6c2b rename template files 2021-04-20 18:36:53 +06:00
a2b87e63c9 start 0.4.36 2021-04-20 18:36:10 +06:00
d95c283653 crutch for nodejs 2021-04-18 17:29:02 +06:00
6a9ae0c148 Merge pull request #60 from InsanusMokrassar/0.4.35
hotfixes
2021-04-15 14:31:28 +06:00
040dd517d8 hotfixes 2021-04-15 14:30:45 +06:00
9663c1ca64 Merge pull request #59 from InsanusMokrassar/0.4.35
0.4.35
2021-04-15 14:19:31 +06:00
ffc2d23be7 add project typed serializer 2021-04-15 14:15:40 +06:00
49b009e59b update exposed 2021-04-15 13:49:53 +06:00
778b6a555b start 0.4.35 2021-04-15 13:48:52 +06:00
b3730998e9 Merge pull request #58 from InsanusMokrassar/0.4.34
0.4.34
2021-04-13 12:28:25 +06:00
48 changed files with 560 additions and 371 deletions

12
.github/workflows/build.yml vendored Normal file
View File

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

View File

@@ -18,7 +18,7 @@ jobs:
- name: Build - name: Build
run: ./gradlew build run: ./gradlew build
- name: Publish - name: Publish
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signMetadataPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
env: env:
GITHUBPACKAGES_USER: ${{ github.actor }} GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,5 +1,102 @@
# Changelog # Changelog
## 0.5.8
* `Common`:
* New extension `Iterable#firstNotNull`
* `Coroutines`
* New extension `Flow#firstNotNull`
* New extensions `CoroutineContext#LinkedSupervisorJob`, `CoroutineScope#LinkedSupervisorJob` and
`CoroutineScope#LinkedSupervisorScope`
## 0.5.7
* `Pagination`
* `Ktor`
* `Server`
* Fixes in extension `extractPagination`
* `Repos`
* `Cache`
* All standard cache repos have been separated to read and read/write repos
## 0.5.6
* `Versions`
* `Exposed`: `0.31.1` -> `0.32.1`
* `Coroutines`
* `JVM`
* `launchSynchronously` and subsequent functions got improved mechanism
* New method `safelyWithResult`
## 0.5.5
* `Versions`
* `Ktor`: `1.5.4` -> `1.6.0`
## 0.5.4
* `Versions`:
* `Klock`: `2.1.0` -> `2.1.2`
## 0.5.3
* `Versions`:
* `Kotlin`: `1.5.0` -> `1.5.10`
* `Coroutines`:
* Extensions `doInUI` and `doInDefault` were replaced in common and available on any supported platform
* Extension `doInIO` replaced into `jvm` and available on any `JVM` platform
* Old extension `safelyWithouException` without `onException` has been replaced by its copy with `onException` and
default value
* New value `defaultSafelyWithoutExceptionHandlerWithNull` which is used in all `*WithoutExceptions` by default
* Analogs of `launch` and `async` for `safely` and `safelyWithoutExceptions` were added
* Analogs of `runCatching` for `safely` and `safelyWithoutExceptions` were added
## 0.5.2
* `Ktor`:
* `Client`:
* Fixes in `UnifiedRequester`
## 0.5.1
* `Versions`:
* `Kotlin Serialization`: `1.2.0` -> `1.2.1`
## 0.5.0
**Notice**: This version is still depend on Kotlin
Exposed 0.31.1. That means that this version
may work improperly in modules based on Kotlin
Exposed
* `Versions`:
* `Kotlin Exposed`: `0.30.2` -> `0.31.1`
* `Kotlin Coroutines`: `1.4.3` -> `1.5.0`
* `RecyclerView`: `1.1.0` -> `1.2.0`
* `Ktor`: `1.5.3` -> `1.5.4`
* `Klock`: `2.0.7` -> `2.1.0`
* `UUID`: `0.2.4` -> `0.3.0`
* **ALL DEPRECATIONS WERE REMOVED**
* `Android`:
* `Alerts`:
* `RecyclerView`:
* Classes `ActionViewHolder` and `ActionsRecyclerViewAdapter` became public
* `Coroutines`:
* New extension and function `doSynchronously` which are the same as `launchSynchronously`
* New extensions `doInDefault` and `doInIO`
## 0.4.36
* All `Android` targets inside common mpp modules now includes JVM code
## 0.4.35
* `Versions`:
* `Kotlin Exposed`: `0.30.1` -> `0.30.2`
* `Serialization`:
* `TypedSerializer`:
* Project has been inited
## 0.4.34 ## 0.4.34
* `Versions`: * `Versions`:

View File

@@ -15,7 +15,7 @@ data class AlertAction(
val callback: (DialogInterface) -> Unit val callback: (DialogInterface) -> Unit
) )
private class ActionViewHolder( class ActionViewHolder(
container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface
) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) { ) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) {
private lateinit var action: AlertAction private lateinit var action: AlertAction
@@ -34,7 +34,7 @@ private class ActionViewHolder(
} }
} }
private class ActionsRecyclerViewAdapter( class ActionsRecyclerViewAdapter(
data: List<AlertAction>, data: List<AlertAction>,
private val dialogInterfaceGetter: () -> DialogInterface private val dialogInterfaceGetter: () -> DialogInterface
) : RecyclerViewAdapter<AlertAction>(data) { ) : RecyclerViewAdapter<AlertAction>(data) {

View File

@@ -8,7 +8,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.2' classpath 'com.android.tools.build:gradle:4.1.3'
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.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
@@ -25,6 +25,15 @@ allprojects {
google() google()
maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "https://kotlin.bintray.com/kotlinx" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed
if (it != rootProject.findProject("docs")) {
tasks.whenTaskAdded { task ->
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
task.enabled = false
}
}
}
} }
apply from: "./extensions.gradle" apply from: "./extensions.gradle"

View File

@@ -1,89 +0,0 @@
package dev.inmo.micro_utils.common
import kotlin.experimental.and
private const val BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
private const val BASE64_MASK: Byte = 0x3f
private const val BASE64_PAD = '='
private val BASE64_INVERSE_ALPHABET = IntArray(256) {
BASE64_ALPHABET.indexOf(it.toChar())
}
internal fun Int.toBase64(): Char = BASE64_ALPHABET[this]
internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK
const val replacedInCrypto = "Replaced in crypto subproject"
@Deprecated(replacedInCrypto, ReplaceWith("EncodedBase64String", "dev.inmo.micro_utils.crypto"))
typealias EncodedBase64String = String
@Deprecated(replacedInCrypto, ReplaceWith("EncodedByteArray", "dev.inmo.micro_utils.crypto"))
typealias EncodedByteArray = ByteArray
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64String()", "dev.inmo.micro_utils.crypto"))
fun String.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64()", "dev.inmo.micro_utils.crypto"))
fun String.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64String()", "dev.inmo.micro_utils.crypto"))
fun ByteArray.encodeBase64String(): EncodedBase64String = buildString {
var i = 0
while (this@encodeBase64String.size > i) {
val read = kotlin.math.min(3, this@encodeBase64String.size - i)
val data = ByteArray(3) {
if (it < read) {
this@encodeBase64String[it + i]
} else {
0
}
}
val padSize = (data.size - read) * 8 / 6
val chunk = ((data[0].toInt() and 0xFF) shl 16) or
((data[1].toInt() and 0xFF) shl 8) or
(data[2].toInt() and 0xFF)
for (index in data.size downTo padSize) {
val char = (chunk shr (6 * index)) and BASE64_MASK.toInt()
append(char.toBase64())
}
repeat(padSize) { append(BASE64_PAD) }
i += read
}
}
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64()", "dev.inmo.micro_utils.crypto"))
fun ByteArray.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64()", "dev.inmo.micro_utils.crypto"))
fun EncodedBase64String.decodeBase64() = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64String()", "dev.inmo.micro_utils.crypto"))
fun EncodedBase64String.decodeBase64String() = decodeBase64().decodeToString()
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64()", "dev.inmo.micro_utils.crypto"))
fun EncodedByteArray.decodeBase64(): ByteArray {
val result = mutableListOf<Byte>()
val data = ByteArray(4)
(0 until size step 4).forEach { i ->
var read = 0
for (j in 0 until 4) {
if (j + i < size) {
data[j] = get(j + i)
read++
} else {
break
}
}
val chunk = data.foldIndexed(0) { index, result, current ->
result or (current.fromBase64().toInt() shl ((3 - index) * 6))
}
for (index in data.size - 2 downTo (data.size - read)) {
val origin = (chunk shr (8 * index)) and 0xff
result.add(origin.toByte())
}
}
return result.toByteArray()
}
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64String()", "dev.inmo.micro_utils.crypto"))
fun EncodedByteArray.decodeBase64String() = decodeBase64().decodeToString()

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.common
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!

View File

@@ -1,33 +0,0 @@
package dev.inmo.micro_utils.common
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
class Base64Text {
val testText = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"""
val base64Text = """TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpTZWQgdXQgcGVyc3BpY2lhdGlzIHVuZGUgb21uaXMgaXN0ZSBuYXR1cyBlcnJvciBzaXQgdm9sdXB0YXRlbSBhY2N1c2FudGl1bSBkb2xvcmVtcXVlIGxhdWRhbnRpdW0sIHRvdGFtIHJlbSBhcGVyaWFtLCBlYXF1ZSBpcHNhIHF1YWUgYWIgaWxsbyBpbnZlbnRvcmUgdmVyaXRhdGlzIGV0IHF1YXNpIGFyY2hpdGVjdG8gYmVhdGFlIHZpdGFlIGRpY3RhIHN1bnQgZXhwbGljYWJvLiBOZW1vIGVuaW0gaXBzYW0gdm9sdXB0YXRlbSBxdWlhIHZvbHVwdGFzIHNpdCBhc3Blcm5hdHVyIGF1dCBvZGl0IGF1dCBmdWdpdCwgc2VkIHF1aWEgY29uc2VxdXVudHVyIG1hZ25pIGRvbG9yZXMgZW9zIHF1aSByYXRpb25lIHZvbHVwdGF0ZW0gc2VxdWkgbmVzY2l1bnQuIE5lcXVlIHBvcnJvIHF1aXNxdWFtIGVzdCwgcXVpIGRvbG9yZW0gaXBzdW0gcXVpYSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIsIGFkaXBpc2NpIHZlbGl0LCBzZWQgcXVpYSBub24gbnVtcXVhbSBlaXVzIG1vZGkgdGVtcG9yYSBpbmNpZHVudCB1dCBsYWJvcmUgZXQgZG9sb3JlIG1hZ25hbSBhbGlxdWFtIHF1YWVyYXQgdm9sdXB0YXRlbS4gVXQgZW5pbSBhZCBtaW5pbWEgdmVuaWFtLCBxdWlzIG5vc3RydW0gZXhlcmNpdGF0aW9uZW0gdWxsYW0gY29ycG9yaXMgc3VzY2lwaXQgbGFib3Jpb3NhbSwgbmlzaSB1dCBhbGlxdWlkIGV4IGVhIGNvbW1vZGkgY29uc2VxdWF0dXI/IFF1aXMgYXV0ZW0gdmVsIGV1bSBpdXJlIHJlcHJlaGVuZGVyaXQgcXVpIGluIGVhIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIHF1YW0gbmloaWwgbW9sZXN0aWFlIGNvbnNlcXVhdHVyLCB2ZWwgaWxsdW0gcXVpIGRvbG9yZW0gZXVtIGZ1Z2lhdCBxdW8gdm9sdXB0YXMgbnVsbGEgcGFyaWF0dXI/"""
@Test
fun decodeEncode() {
val encoded = testText.encodeBase64String()
assertEquals(base64Text, encoded)
val decoded = encoded.decodeBase64String()
assertEquals(testText, decoded)
}
val urlTestText = "https://example.com?isitexample=1"
val urlTestBase64Text = "aHR0cHM6Ly9leGFtcGxlLmNvbT9pc2l0ZXhhbXBsZT0x"
@Test
fun decodeEncodeUrl() {
val encoded = urlTestText.encodeBase64String()
assertEquals(urlTestBase64Text, encoded)
val decoded = encoded.decodeBase64String()
assertEquals(urlTestText, decoded)
}
}

View File

@@ -19,4 +19,4 @@ kotlin {
} }
} }
} }
} }

View File

@@ -38,7 +38,7 @@ inline fun CoroutineScope.createSafeActionsActor(
suspend fun <T> Channel<suspend () -> Unit>.doWithSuspending( suspend fun <T> Channel<suspend () -> Unit>.doWithSuspending(
action: ActorAction<T> action: ActorAction<T>
) = suspendCoroutine<T> { ) = suspendCoroutine<T> {
offer { trySend {
safely({ e -> it.resumeWithException(e) }) { safely({ e -> it.resumeWithException(e) }) {
it.resume(action()) it.resume(action())
} }

View File

@@ -0,0 +1,23 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
inline val UI
get() = Dispatchers.Main
inline val Default
get() = Dispatchers.Default
suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext(
context,
block
)
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn(
UI,
block
)
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn(
Default,
block
)

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!

View File

@@ -22,34 +22,6 @@ var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
} }
} }
/**
* Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default
* [SafelyExceptionHandler]
*/
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
class SafelyExceptionHandlerKey<T> : CoroutineContext.Key<SafelyExceptionHandler<T>>
/**
* Shortcut for creating instance of [SafelyExceptionHandlerKey]
*/
@Suppress("NOTHING_TO_INLINE")
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
inline fun <T> safelyExceptionHandlerKey() = SafelyExceptionHandlerKey<T>()
/**
* Wrapper for [ExceptionHandler] which can be used in [CoroutineContext] to set local (for [CoroutineContext]) default
* [ExceptionHandler]. To get it use [CoroutineContext.get] with key [SafelyExceptionHandlerKey]
*
* @see SafelyExceptionHandlerKey
* @see ExceptionHandler
*/
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandler", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
class SafelyExceptionHandler<T>(
val handler: ExceptionHandler<T>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*> = safelyExceptionHandlerKey<T>()
}
/** /**
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions * This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key] * and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
@@ -114,6 +86,9 @@ suspend fun <T> safelyWithContextExceptionHandler(
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key * * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [defaultSafelyExceptionHandler] * * [defaultSafelyExceptionHandler]
* *
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
* be called [onException]
*
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this * @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
* exception will be available for catching * exception will be available for catching
* *
@@ -133,24 +108,42 @@ suspend inline fun <T> safely(
} }
} }
suspend inline fun <T> runCatchingSafely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatching {
safely(onException, block)
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
/**
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
* returning null at one time
*
* @see safelyWithoutExceptions
* @see launchSafelyWithoutExceptions
* @see asyncSafelyWithoutExceptions
*/
val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
defaultSafelyWithoutExceptionHandler.invoke(it)
null
}
/** /**
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of * Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
* result from exception (instead of throwing it) * result from exception (instead of throwing it, by default always returns null)
*/ */
suspend inline fun <T> safelyWithoutExceptions( suspend inline fun <T> safelyWithoutExceptions(
noinline onException: ExceptionHandler<T?>, noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T noinline block: suspend CoroutineScope.() -> T
): T? = safely(onException, block) ): T? = safely(onException, block)
/** suspend inline fun <T> runCatchingSafelyWithoutExceptions(
* Shortcut for [safely] without exception handler (instead of this you will always receive null as a result) noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
*/
suspend inline fun <T> safelyWithoutExceptions(
noinline block: suspend CoroutineScope.() -> T noinline block: suspend CoroutineScope.() -> T
): T? = safelyWithoutExceptions( ): Result<T?> = runCatching {
{ safelyWithoutExceptions(onException, block)
defaultSafelyWithoutExceptionHandler.invoke(it) }
null
},
block
)

View File

@@ -0,0 +1,41 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
inline fun CoroutineScope.launchSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safely(onException, block)
}
inline fun CoroutineScope.launchSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safelyWithoutExceptions(onException, block)
}
inline fun <T> CoroutineScope.asyncSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
) = async(context, start) {
safely(onException, block)
}
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
) = async(context, start) {
safelyWithoutExceptions(onException, block)
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
fun CoroutineContext.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
fun CoroutineScope.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorJob(additionalContext)
fun CoroutineScope.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = CoroutineScope(
coroutineContext + LinkedSupervisorJob(additionalContext)
)

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
val IO
get() = Dispatchers.IO
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn(
IO,
block
)

View File

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

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.launchSynchronously
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.test.* import kotlin.test.Test
import kotlin.test.assertEquals
class DoWithFirstTests { class DoWithFirstTests {
@Test @Test

View File

@@ -1,10 +0,0 @@
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
)

View File

@@ -1,5 +1,30 @@
apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.getkeepsafe.dexcount'
ext {
jvmKotlinFolderFile = {
String sep = File.separator
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
}
enableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
if (jvmKotlinFolder.exists()) {
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
}
}
disableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
String[] oldDirs = android.sourceSets.main.java.srcDirs
android.sourceSets.main.java.srcDirs = []
for (oldDir in oldDirs) {
if (oldDir != jvmKotlinFolder.path) {
android.sourceSets.main.java.srcDirs += oldDir
}
}
}
}
android { android {
compileSdkVersion "$android_compileSdkVersion".toInteger() compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion" buildToolsVersion "$android_buildToolsVersion"
@@ -35,6 +60,8 @@ android {
} }
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' String sep = File.separator
main.java.srcDirs += "src${sep}main${sep}kotlin"
enableIncludingJvmCodeInAndroidPart()
} }
} }

View File

@@ -20,11 +20,11 @@ allprojects {
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true" releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings" defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle" publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"

View File

@@ -5,29 +5,30 @@ kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g
kotlin_version=1.4.32 kotlin_version=1.5.10
kotlin_coroutines_version=1.4.3 kotlin_coroutines_version=1.5.0
kotlin_serialisation_core_version=1.1.0 kotlin_serialisation_core_version=1.2.1
kotlin_exposed_version=0.30.1 kotlin_exposed_version=0.32.1
ktor_version=1.5.3 ktor_version=1.6.0
klockVersion=2.0.7 klockVersion=2.1.2
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12
uuidVersion=0.2.4 uuidVersion=0.3.0
# ANDROID # ANDROID
core_ktx_version=1.3.2 core_ktx_version=1.3.2
androidx_recycler_version=1.1.0 androidx_recycler_version=1.2.0
appcompat_version=1.2.0 appcompat_version=1.2.0
android_minSdkVersion=19 android_minSdkVersion=19
android_compileSdkVersion=30 android_compileSdkVersion=30
android_buildToolsVersion=30.0.2 android_buildToolsVersion=30.0.3
dexcount_version=2.0.0 dexcount_version=2.0.0
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.2 test_ext_junit_version=1.1.2
@@ -39,10 +40,10 @@ crypto_js_version=4.0.0
# Dokka # Dokka
dokka_version=1.4.30 dokka_version=1.4.32
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.4.34 version=0.5.8
android_code_version=38 android_code_version=49

View File

@@ -15,30 +15,21 @@ class UnifiedRequester(
suspend fun <ResultType> uniget( suspend fun <ResultType> uniget(
url: String, url: String,
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.get<StandardKtorSerialInputData>( ): ResultType = client.uniget(url, resultDeserializer, serialFormat)
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> encodeUrlQueryValue( fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>, serializationStrategy: SerializationStrategy<T>,
value: T value: T
) = serialFormat.encodeHex( ) = serializationStrategy.encodeUrlQueryValue(
serializationStrategy, value,
value serialFormat
) )
suspend fun <BodyType, ResultType> unipost( suspend fun <BodyType, ResultType> unipost(
url: String, url: String,
bodyInfo: BodyPair<BodyType>, bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>
) = client.post<StandardKtorSerialInputData>(url) { ) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
@@ -51,14 +42,30 @@ val defaultRequester = UnifiedRequester()
suspend fun <ResultType> HttpClient.uniget( suspend fun <ResultType> HttpClient.uniget(
url: String, url: String,
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>,
) = defaultRequester.uniget(url, resultDeserializer) serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value) fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = serialFormat.encodeHex(
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>,
) = defaultRequester.unipost(url, bodyInfo, resultDeserializer) serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.collect
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() { private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
if (incoming.poll() != null) { if (incoming.tryReceive() != null) {
close() close()
throw CorrectCloseException throw CorrectCloseException
} }

View File

@@ -5,8 +5,8 @@ import io.ktor.http.Parameters
val Parameters.extractPagination: Pagination val Parameters.extractPagination: Pagination
get() = SimplePagination( get() = SimplePagination(
get("page") ?.toIntOrNull() ?: 0, get(paginationPageKey) ?.toIntOrNull() ?: 0,
get("size") ?.toIntOrNull() ?: defaultPaginationPageSize get(paginationSizeKey) ?.toIntOrNull() ?: defaultPaginationPageSize
) )
val ApplicationCall.extractPagination: Pagination val ApplicationCall.extractPagination: Pagination

View File

@@ -6,19 +6,29 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>( open class ReadCRUDCacheRepo<ObjectType, IdType>(
protected val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>, protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected val kvCache: KVCache<IdType, ObjectType>, protected val kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected val idGetter: (ObjectType) -> IdType protected val idGetter: (ObjectType) -> IdType
) : CRUDRepo<ObjectType, IdType, InputValueType> by parentRepo { ) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also { override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
kvCache.set(id, it) kvCache.set(id, it)
}) })
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
} }
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
) : ReadCRUDCacheRepo<ObjectType, IdType>(
parentRepo,
kvCache,
idGetter
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -7,14 +7,19 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
open class KeyValueCacheRepo<Key,Value>( open class ReadKeyValueCacheRepo<Key,Value>(
protected val parentRepo: KeyValueRepo<Key, Value>, protected val parentRepo: ReadKeyValueRepo<Key, Value>,
protected val kvCache: KVCache<Key, Value>, protected val kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) : ReadKeyValueRepo<Key,Value> by parentRepo {
) : KeyValueRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
} }
open class KeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>,
kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -11,15 +11,10 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
open class KeyValuesCacheRepo<Key,Value>( open class ReadKeyValuesCacheRepo<Key,Value>(
protected val parentRepo: KeyValuesRepo<Key, Value>, protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected val kvCache: KVCache<Key, List<Value>>, protected val kvCache: KVCache<Key, List<Value>>
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) : ReadKeyValuesRepo<Key,Value> by parentRepo {
) : KeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return kvCache.get(k) ?.paginate( return kvCache.get(k) ?.paginate(
pagination.let { if (reversed) it.reverse(count(k)) else it } pagination.let { if (reversed) it.reverse(count(k)) else it }
@@ -35,3 +30,13 @@ open class KeyValuesCacheRepo<Key,Value>(
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v) override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
} }
open class KeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>,
kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -30,4 +30,6 @@ kotlin {
} }
} }
} }
} }
disableIncludingJvmCodeInAndroidPart()

View File

@@ -5,26 +5,6 @@ import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
@Deprecated("Will be removed soon due to redundancy. Can be replaced with other doForAll extensions")
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
crossinline block: (List<T>) -> Unit
) {
doForAllWithNextPaging {
methodCaller(it).also {
block(it.results)
}
}
}
@Deprecated("Will be removed soon due to redundancy. Can be replaced with other doForAll extensions")
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
crossinline block: (List<T>) -> Unit
) = doForAllWithNextPaging {
getByPagination(it).also { block(it.results) }
}
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll( suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>

View File

@@ -4,24 +4,6 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
block: (List<Pair<Key, Value>>) -> Unit
) {
doWithPagination {
methodCaller(it).also { keys ->
block(keys.results.mapNotNull { key -> get(key) ?.let { value -> key to value } })
}.nextPageIfNotEmpty()
}
}
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
block: (List<Pair<Key, Value>>) -> Unit
) = doForAll({ keys(it, false) }, block)
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll( suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>

View File

@@ -4,34 +4,6 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
block: (List<Pair<Key, List<Value>>>) -> Unit
) {
doWithPagination {
methodCaller(it).also { keys ->
block(
keys.results.mapNotNull { key ->
val values = mutableListOf<Value>()
doWithPagination {
get(key, it).also {
values.addAll(it.results)
}.nextPageIfNotEmpty()
}
key to values
}
)
}.nextPageIfNotEmpty()
}
}
@Deprecated("Will be removed soon due to redundancy")
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
block: (List<Pair<Key, List<Value>>>) -> Unit
) = doForAll({ keys(it, false) }, block)
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll( suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>

View File

@@ -18,19 +18,16 @@ class StandardVersionsRepo<T>(
onCreate: suspend T.() -> Unit, onCreate: suspend T.() -> Unit,
onUpdate: suspend T.(from: Int, to: Int) -> Unit onUpdate: suspend T.(from: Int, to: Int) -> Unit
) { ) {
var savedVersion = proxy.getTableVersion(tableName) var currentVersion = proxy.getTableVersion(tableName)
if (savedVersion == null) { if (currentVersion == null) {
proxy.database.onCreate() proxy.database.onCreate()
proxy.updateTableVersion(tableName, version) }
} else { while (currentVersion == null || currentVersion < version) {
while (savedVersion != null && savedVersion < version) { val oldVersion = currentVersion ?: 0
val newVersion = savedVersion + 1 currentVersion = oldVersion + 1
proxy.database.onUpdate(oldVersion, currentVersion)
proxy.database.onUpdate(savedVersion, newVersion) proxy.updateTableVersion(tableName, currentVersion)
proxy.updateTableVersion(tableName, newVersion)
savedVersion = newVersion
}
} }
} }
} }

View File

@@ -5,6 +5,7 @@ import android.database.DatabaseErrorHandler
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
import dev.inmo.micro_utils.repos.versions.* import dev.inmo.micro_utils.repos.versions.*
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@@ -15,7 +16,8 @@ class StandardSQLHelper(
name: String, name: String,
factory: SQLiteDatabase.CursorFactory? = null, factory: SQLiteDatabase.CursorFactory? = null,
version: Int = 1, version: Int = 1,
errorHandler: DatabaseErrorHandler? = null errorHandler: DatabaseErrorHandler? = null,
useSharedPreferencesForVersions: Boolean = false
) { ) {
val sqlOpenHelper = object : SQLiteOpenHelper(context, name, factory, version, errorHandler) { val sqlOpenHelper = object : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
override fun onCreate(db: SQLiteDatabase?) {} override fun onCreate(db: SQLiteDatabase?) {}
@@ -23,7 +25,16 @@ class StandardSQLHelper(
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {} override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}
} }
val versionsRepo: VersionsRepo<SQLiteOpenHelper> by lazy { val versionsRepo: VersionsRepo<SQLiteOpenHelper> by lazy {
StandardVersionsRepo(AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper)) StandardVersionsRepo(
if (useSharedPreferencesForVersions) {
KeyValueBasedVersionsRepoProxy(
context.keyValueStore("AndroidSPStandardVersionsRepo"),
sqlOpenHelper
)
} else {
AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper)
}
)
} }
suspend fun <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block) suspend fun <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)

View File

@@ -50,7 +50,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
override suspend fun deleteById(ids: List<IdType>) { override suspend fun deleteById(ids: List<IdType>) {
val deleted = mutableListOf<IdType>() val deleted = mutableListOf<IdType>()
helper.blockingWritableTransaction { helper.blockingWritableTransaction {
ids.forEach { id -> for (id in ids) {
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also { delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
if (it > 0) { if (it > 0) {
deleted.add(id) deleted.add(id)
@@ -58,8 +58,8 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
} }
} }
} }
deleted.forEach { for (deletedItem in deleted) {
deleteObjectsIdsChannel.emit(it) deleteObjectsIdsChannel.emit(deletedItem)
} }
} }

View File

@@ -42,9 +42,9 @@ class KeyValueStore<T : Any> internal constructor (
init { init {
cachedData ?.let { cachedData ?.let {
sharedPreferences.all.forEach { for ((key, value) in sharedPreferences.all) {
if (it.value != null) { if (value != null) {
cachedData[it.key] = it.value as Any cachedData[key] = value
} }
} }
sharedPreferences.registerOnSharedPreferenceChangeListener(this) sharedPreferences.registerOnSharedPreferenceChangeListener(this)
@@ -113,7 +113,7 @@ class KeyValueStore<T : Any> internal constructor (
override suspend fun set(toSet: Map<String, T>) { override suspend fun set(toSet: Map<String, T>) {
sharedPreferences.edit { sharedPreferences.edit {
toSet.forEach { (k, v) -> for ((k, v) in toSet) {
when(v) { when(v) {
is Int -> putInt(k, v) is Int -> putInt(k, v)
is Long -> putLong(k, v) is Long -> putLong(k, v)
@@ -127,16 +127,20 @@ class KeyValueStore<T : Any> internal constructor (
} }
} }
} }
toSet.forEach { (k, v) -> for ((k, v) in toSet) {
onNewValueChannel.emit(k to v) onNewValueChannel.emit(k to v)
} }
} }
override suspend fun unset(toUnset: List<String>) { override suspend fun unset(toUnset: List<String>) {
sharedPreferences.edit { sharedPreferences.edit {
toUnset.forEach { remove(it) } for (item in toUnset) {
remove(item)
}
}
for (it in toUnset) {
_onValueRemovedFlow.emit(it)
} }
toUnset.forEach { _onValueRemovedFlow.emit(it) }
} }
override suspend fun unsetWithValues(toUnset: List<T>) { override suspend fun unsetWithValues(toUnset: List<T>) {

View File

@@ -55,7 +55,7 @@ class OneToManyAndroidRepo<Key, Value>(
override suspend fun add(toAdd: Map<Key, List<Value>>) { override suspend fun add(toAdd: Map<Key, List<Value>>) {
val added = mutableListOf<Pair<Key, Value>>() val added = mutableListOf<Pair<Key, Value>>()
helper.blockingWritableTransaction { helper.blockingWritableTransaction {
toAdd.forEach { (k, values) -> for ((k, values) in toAdd) {
values.forEach { v -> values.forEach { v ->
insert( insert(
tableName, tableName,

View File

@@ -21,14 +21,12 @@ class AndroidSQLStandardVersionsRepoProxy(
private val tableVersionColumnName = "version" private val tableVersionColumnName = "version"
init { init {
runBlocking(DatabaseCoroutineContext) { database.blockingWritableTransaction {
database.writableTransaction { createTable(
createTable( tableName,
tableName, tableNameColumnName to ColumnType.Text.NOT_NULLABLE,
tableNameColumnName to ColumnType.Text.NOT_NULLABLE, tableVersionColumnName to ColumnType.Numeric.INTEGER()
tableVersionColumnName to ColumnType.Numeric.INTEGER() )
)
}
} }
} }

View File

@@ -24,9 +24,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow() override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow() override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
@Deprecated("Will be removed in near major update. Override open fun with the same name instead") protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
abstract val InsertStatement<Number>.asObject: ObjectType
protected open fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType = asObject
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean> 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>)

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.common.one_to_many
@Deprecated("Replaced", ReplaceWith("keyParameterName", "dev.inmo.micro_utils.repos.ktor.common.keyParameterName"))
const val keyParameterName = dev.inmo.micro_utils.repos.ktor.common.keyParameterName
@Deprecated("Replaced", ReplaceWith("valueParameterName", "dev.inmo.micro_utils.repos.ktor.common.valueParameterName"))
const val valueParameterName = dev.inmo.micro_utils.repos.ktor.common.valueParameterName
@Deprecated("Replaced", ReplaceWith("reversedParameterName", "dev.inmo.micro_utils.repos.ktor.common.reversedParameterName"))
const val reversedParameterName = dev.inmo.micro_utils.repos.ktor.common.reversedParameterName

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@ String[] includes = [
":android:alerts:recyclerview", ":android:alerts:recyclerview",
":serialization:base64", ":serialization:base64",
":serialization:encapsulator", ":serialization:encapsulator",
":serialization:typed_serializer",
":dokka" ":dokka"
] ]