Compare commits

...

176 Commits

Author SHA1 Message Date
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
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
837758aebe update uuid 2021-04-13 12:22:04 +06:00
6ac4149aa1 fixes in crud repos 2021-04-13 12:20:24 +06:00
278584ae6a start 0.4.34 2021-04-13 12:10:55 +06:00
126f9d5f41 Merge pull request #57 from InsanusMokrassar/0.4.33
0.4.33
2021-04-05 16:30:58 +06:00
ce15ff4e0a update ktor 2021-04-05 16:22:39 +06:00
382b956beb make createWeakSubScope private, upfill readme 2021-04-05 16:20:11 +06:00
36deab4909 weak jobs workaround 2021-04-05 16:10:36 +06:00
550fc59d9d start 0.4.33 2021-04-05 15:14:21 +06:00
9100a57458 Merge pull request #56 from InsanusMokrassar/0.4.32
0.4.32
2021-04-03 14:07:01 +06:00
74d9bbccd9 updates 2021-04-02 18:43:56 +06:00
75e602a349 update kotlin exposed 2021-04-02 18:39:56 +06:00
e73644db10 start 0.4.32 2021-04-02 18:26:53 +06:00
148e6bdae7 Merge pull request #55 from InsanusMokrassar/0.4.31
0.4.31
2021-03-29 20:12:11 +06:00
1b540199f0 small update 2021-03-29 19:51:38 +06:00
30b70e9984 replacement of doForAll and getAll 2021-03-29 19:48:37 +06:00
c1557cff27 huge update with crud caching, common doForAll/getAll and deprecations in old ones doForAll 2021-03-29 19:39:10 +06:00
2d662f91b3 updates in coroutines 2021-03-29 17:57:36 +06:00
c4a08e52e5 update kotlin 2021-03-24 13:11:15 +06:00
08c371c142 add repos.cache 2021-03-24 13:01:15 +06:00
8e62dd460c start 0.4.31 2021-03-23 13:46:35 +06:00
1f9302dc94 Merge pull request #54 from InsanusMokrassar/0.4.30
0.4.30
2021-03-17 14:33:15 +06:00
16f445f699 update klock 2021-03-17 13:52:54 +06:00
b4abd564ec update paginations 2021-03-17 13:48:58 +06:00
14ffafb0a7 start 0.4.30 2021-03-17 13:43:52 +06:00
e0cc780887 Merge pull request #52 from InsanusMokrassar/0.4.29
0.4.29
2021-03-05 20:45:34 +06:00
ab7d277167 add note about transactions improvements 2021-03-05 20:42:30 +06:00
8308c1df4d update gradlewrapper up to 6.8.3 2021-03-05 19:12:26 +06:00
12c29f5180 Update packages_push.yml 2021-03-05 18:37:10 +06:00
2963098870 update publishing scripts 2021-03-05 16:43:50 +06:00
9f56b0a26d skip signing on publishing to packages 2021-03-05 16:34:16 +06:00
75851312fd remove old build yml 2021-03-05 16:24:43 +06:00
3a3be138a5 add packages publishing yml 2021-03-05 16:24:22 +06:00
fb7d1f18b0 update publication scripts 2021-03-05 16:21:36 +06:00
d1c6c7696a android database improvements 2021-03-05 16:15:21 +06:00
5f38d9635d update version of coroutines 2021-03-05 13:05:38 +06:00
8185ea87b1 start 0.4.29 2021-03-05 13:04:19 +06:00
98bd07d025 Merge pull request #50 from InsanusMokrassar/0.4.28
0.4.28
2021-03-02 01:09:48 +06:00
c502c70a21 should fix lint errors 2021-03-02 00:54:21 +06:00
d933dc532b add actions actor 2021-03-01 18:35:11 +06:00
42594f0656 update dependencies 2021-03-01 18:23:20 +06:00
4b83ca19c3 start 0.4.28 2021-03-01 18:21:34 +06:00
bab13f5e83 Update CHANGELOG.md 2021-02-26 15:35:15 +06:00
14c5f5a26c Merge pull request #48 from InsanusMokrassar/0.4.27
0.4.27
2021-02-23 13:09:04 +06:00
b26a4f24d4 fix in AbstractExposedWriteCRUDRepo 2021-02-23 12:51:13 +06:00
498ec673dc start 0.4.27 2021-02-23 12:50:55 +06:00
5c67ab6aec Merge pull request #47 from InsanusMokrassar/0.4.26
0.4.26
2021-02-21 21:36:09 +06:00
f78359b5d7 update serialization 2021-02-21 21:19:13 +06:00
dbce612cb2 start 0.4.26 2021-02-21 21:16:45 +06:00
4322ffdb0a Merge pull request #46 from InsanusMokrassar/0.4.25
0.4.25
2021-02-11 15:12:39 +06:00
123b848d74 update matrix test 2021-02-11 15:12:21 +06:00
b120fbd2b1 remove add and put column method in matrix row builder 2021-02-11 15:07:24 +06:00
d41c3c2de4 updates in matrix 2021-02-11 15:05:11 +06:00
a3cabd7a9e add matrix 2021-02-11 14:43:07 +06:00
3dde486126 start 0.4.25 2021-02-11 14:31:05 +06:00
5978122dda update scripts 2021-02-06 00:51:31 +06:00
4b4806ce34 Merge pull request #45 from InsanusMokrassar/0.4.24
0.4.24
2021-02-05 16:11:09 +06:00
987c6cc709 update android version 2021-02-05 16:10:08 +06:00
012c7e9bdf add inline functions in DoWithFirstBuilder 2021-02-05 16:09:08 +06:00
d06bb265e5 update publishing scripts 2021-02-05 15:47:12 +06:00
e2fb8bf21e update depdendencies 2021-02-05 15:42:46 +06:00
60dea2a518 DoWithFirstBuilder 2021-02-05 15:38:20 +06:00
3877f49278 start 0.4.24 2021-02-05 15:35:09 +06:00
a299a5b505 Merge pull request #44 from InsanusMokrassar/0.4.23
0.4.23
2021-01-28 15:45:24 +06:00
a03f7201d2 update ktor 2021-01-28 15:43:06 +06:00
94e26ee8a0 add Base64BytesToFromStringSerializer 2021-01-28 15:38:22 +06:00
2d8ad47083 start 0.4.23 2021-01-28 15:04:49 +06:00
e9aec238a1 Merge pull request #43 from InsanusMokrassar/0.4.22
0.4.22
2021-01-28 00:13:28 +06:00
844c33166c update wrapper 2021-01-27 23:57:21 +06:00
cfe9f2159f add build workflow 2021-01-27 23:56:03 +06:00
61bccd5f48 update versions 2021-01-27 23:49:57 +06:00
e5a608a315 start 0.4.22 2021-01-27 23:36:27 +06:00
9b4f35eea1 Merge pull request #42 from InsanusMokrassar/0.4.21
0.4.21
2021-01-23 12:03:13 +06:00
602ae2385b copy base64 test in crypto 2021-01-23 12:02:19 +06:00
48a4246aac fill changelog, deprecate base64 in common and update serialization dependencies 2021-01-23 11:56:58 +06:00
a4020cb484 create crypto project 2021-01-23 11:43:40 +06:00
d074f29b82 start 0.4.21 2021-01-22 23:52:16 +06:00
a67bef6853 Merge pull request #41 from InsanusMokrassar/0.4.20
0.4.20
2021-01-21 10:24:58 +06:00
14e666f18d create encpsulator 2021-01-21 10:10:52 +06:00
cce914091c start 0.4.20 2021-01-21 10:10:03 +06:00
d7cd3db8e2 Merge pull request #40 from InsanusMokrassar/0.4.19
0.4.19
2021-01-19 13:29:36 +06:00
0d8f844314 updates in coroutines 2021-01-19 13:28:50 +06:00
abd0cb2031 start 0.4.19 2021-01-19 13:24:38 +06:00
79ef03ed0c Merge pull request #39 from InsanusMokrassar/0.4.18
0.4.18
2021-01-17 17:17:39 +06:00
a3087cb650 awaitFirst 2021-01-17 17:15:54 +06:00
9acb9af338 Base64ByteArraySerializer 2021-01-17 16:53:24 +06:00
0c9283eb87 start 0.4.18 2021-01-17 16:49:10 +06:00
493d838201 Merge branch '0.4.17' 2021-01-14 21:22:42 +06:00
c0523469fa remove "Serializer" annotation at Base64StringSerializer 2021-01-14 21:22:25 +06:00
4f00eaa6d4 Merge pull request #37 from InsanusMokrassar/0.4.17
0.4.17
2021-01-14 20:55:45 +06:00
9706524572 update readme 2021-01-14 20:55:32 +06:00
3174b84367 base64 tools 2021-01-14 20:51:13 +06:00
6cccd5ff6c start add base64 2021-01-14 14:22:26 +06:00
972857268d start 0.4.17 2021-01-14 14:22:09 +06:00
f3cd92cc5e Merge pull request #36 from InsanusMokrassar/0.4.16
0.4.16
2021-01-08 00:56:23 +06:00
502a49644c safelyWithoutException updates 2021-01-07 10:22:28 +06:00
51719b5868 start 0.4.16 2021-01-07 10:08:39 +06:00
17821bd094 Merge pull request #35 from InsanusMokrassar/0.4.15
0.4.15
2020-12-22 23:18:58 +06:00
6a89ffaa8a rework of context exceptions 2020-12-22 23:17:28 +06:00
d5a8d0f4d4 Revert "unworking version"
This reverts commit 9739bd871e.
2020-12-22 22:45:43 +06:00
9739bd871e unworking version 2020-12-22 22:45:36 +06:00
632d2545d4 start 0.4.15 2020-12-22 21:39:43 +06:00
ecfa273a81 Merge pull request #33 from InsanusMokrassar/0.4.14
0.4.14
2020-12-22 16:00:50 +06:00
a36828116e safely fixes 2020-12-22 15:42:11 +06:00
14aa9ca26c update documentation 2020-12-22 15:21:25 +06:00
069e51f2ff add defaultSafelyExceptionHandler and SafelyExceptionHandler 2020-12-22 15:16:06 +06:00
a15cbdfb1a update versions 2020-12-22 14:55:01 +06:00
4af8114eda start 0.4.14 2020-12-22 14:48:02 +06:00
90dc84e900 Merge pull request #32 from InsanusMokrassar/0.4.13
0.4.13
2020-12-15 13:39:22 +06:00
67c595b440 small refactor in expand/collapse 2020-12-15 13:26:34 +06:00
830b7aee56 horizontal expand/collapse 2020-12-15 13:25:08 +06:00
1890608cb3 start 0.4.13 2020-12-15 12:49:50 +06:00
bd396959a9 Merge pull request #31 from InsanusMokrassar/0.4.12
0.4.12
2020-12-14 21:11:57 +06:00
d5fe19f0a5 update changelog 2020-12-14 20:20:30 +06:00
46bfb09415 launchSynchronously signature update 2020-12-14 20:06:46 +06:00
a60cb596d1 add functionality to selector 2020-12-14 20:01:37 +06:00
6f9d5e2d5f add itemSelectionFlow 2020-12-14 19:45:14 +06:00
80bc226ee1 selector docs and several refactorings 2020-12-14 19:36:07 +06:00
12e37184e1 add selector 2020-12-14 19:17:16 +06:00
25e9345d02 start 0.4.12 2020-12-14 18:44:24 +06:00
ccc4d030c3 Merge pull request #30 from InsanusMokrassar/0.4.11
0.4.11
2020-12-07 22:44:29 +06:00
105 changed files with 2238 additions and 332 deletions

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

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

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

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

View File

@@ -1,5 +1,273 @@
# Changelog # Changelog
## 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
* `Versions`:
* `uuid`: `0.2.3` -> `0.2.4`
* `Repos`:
* `AbstractExposedCRUDRepo` now implements `StandardCRUDRepo`
* `AbstractMutableAndroidCRUDRepo` now implements `StandardCRUDRepo`
## 0.4.33
* `Versions`:
* `Ktor`: `1.5.2` -> `1.5.3`
* `Coroutines`
* Add `WeakJob` workaround:
* `CoroutineScope#weakLaunch`
* `CoroutineScope#weakAsync`
## 0.4.32
* `Versions`:
* `Kotlin Exposed`: `0.29.1` -> `0.30.1`
## 0.4.31
* `Versions`:
* `Kotlin`: `1.4.31` -> `1.4.32`
* `Pagination`:
* New extensions `PaginationResult.changeResultsUnchecked` and `PaginationResult.changeResults` for mapping results
with the same parameters, but different data
* Extension `PaginationResult.thisPageIfNotEmpty` now is typed and will return `PaginationResult?` with the same
generic type as income `PaginationResult`
* New extension `PaginationResult.currentPageIfNotEmpty` - shortcut for `PaginationResult.thisPageIfNotEmpty`
* New common functions. They were created as replacements for currently available for more comfortable work
with repos pagination:
* `doForAll`
* `doForAllWithNextPaging`
* `doForAllWithCurrentPaging`
* `getAll`
* `getAllWithNextPaging`
* `getAllWithCurrentPaging`
* `Coroutines`:
* Rewrite `subscribeSafelyWithoutExceptions`
* Now `subscribeSafelyWithoutExceptions` will use default handler instead of skipping
* New extension `subscribeSafelySkippingExceptions`
* `Repos`
* New subproject `repos.cache` - this subproject will contain repos with data caching mechanisms
* Most old `doForAll` methods have been deprecated
## 0.4.30
* `Versions`:
* `Klock`: `2.0.6` -> `2.0.7`
* `Pagination`:
* New variable `defaultPaginationPageSize` has been added to be able to change default pagination size
* Add new value `firstPageWithOneElementPagination`
## 0.4.29
* `Versions`:
* `Coroutines`: `1.4.2` -> `1.4.3`
* `Repos`:
* `Common`
* `Android`:
* New `blockingReadableTransaction`/`blockingWritableTransaction`
* Android databases realizations now use blocking transactions where it is possible
* Several improvements in transactions work
## 0.4.28
* `Versions`:
* `Kotlin`: `1.4.30` -> `1.4.31`
* `Ktor`: `1.5.1` -> `1.5.2`
* `Coroutines`
* Add `createActionsActor`/`createSafeActionsActor` and `doWithSuspending`
## 0.4.27
* `Repos`
* `Exposed`
* Fix in `AbstractExposedWriteCRUDRepo`
## 0.4.26
* `Versions`:
* `Serialization`: `1.0.1` -> `1.1.0`
## 0.4.25
* `Matrix`:
* Subproject has been created
## 0.4.24
* `Versions`:
* `Kotlin`: `1.4.21` -> `1.4.30`
* `Klock`: `2.0.4` -> `2.0.6`
* `Coroutines`:
* New class `DoWithFirstBuilder`
* Several new extensions like `firstOf`/`first`/`invokeOnFirstOf`
## 0.4.23
* `Versions`:
* `Ktor`: `1.5.0` -> `1.5.1`
* `Serialization`
* `Base64`
* New serializer `Base64BytesToFromStringSerializer` has been added
## 0.4.22
* `Versions`:
* `Exposed`: `0.28.1` -> `0.29.1`
* `Klock`: `2.0.2` -> `2.0.4`
## 0.4.21
* `Common`
* `JS`
* Extension `DataView#toByteArray` has been added
* Extension `ArrayBuffer#toByteArray` has been added
* Extension `ByteArray#toDataView` has been added
* Extension `ByteArray#toArrayBuffer` has been added
* `Coroutines`
* `JS`
* Extension `Blob#toByteArray` has been added
* `Crypto`
* Subproject has been created
* `Serialization`
* `Base64`
* Currently, it is not depended on `common` project and use `crypto` instead
## 0.4.20
* `Serialization`
* `Encapsulator`:
* Has been created
## 0.4.19
* `Coroutines`:
* New extension `Iterable<Deferred>#awaitFirstWithDeferred` has been added to identify which of `Deferred`s was
normally completed
* New extensions `Iterable<Deferred<T>>#invokeOnFirst` and `Iterable<DeferredAction<*, O>>.invokeFirstOf` have been
added
## 0.4.18
* `Coroutines`:
* New extension `Iterable<Deferred>#awaitFirst` has been added
* `Serialization`
* `Base 64`
* New `Base64ByteArraySerializer` has been added
## 0.4.17
* `Common`
* Functionality for decode/encode base 64 to/from `ByteArray`/`String` have been added
* `Serialization`
* `Base 64`
* Project has been initiated
## 0.4.16
* `Coroutines`:
* `safely`:
* New `safelyWithoutExceptions` function may accept `onException` parameter with nullable result
* Old `safelyWithoutExceptions` now using `defaultSafelyWithoutExceptionHandler` to handle exceptions "like in
`safely`", but it is expected that `defaultSafelyWithoutExceptionHandler` will not throw any exception
## 0.4.15
* `Coroutines`:
* `safely`:
* `SafelyExceptionHandlerKey` has been deprecated
* `SafelyExceptionHandler` has been deprecated
* `ContextSafelyExceptionHandlerKey` has been added
* `ContextSafelyExceptionHandler` has been added
* `safelyWithContextExceptionHandler` has been added
## 0.4.14
* `Versions`:
* `Kotlin`: `1.4.20` -> `1.4.21`
* `Ktor`: `1.4.3` -> `1.5.0`
* `Klock`: `2.0.1` -> `2.0.2`
* `Coroutines`:
* Add global variable `defaultSafelyExceptionHandler`
* Add `SafelyExceptionHandlerKey` and `SafelyExceptionHandler` classes to be able to overwrite
`defaultSafelyExceptionHandler` using context of coroutine
## 0.4.13
* `Common`
* `Android`
* Add expand/collapse functionality for horizontal expand/collapse
## 0.4.12
* `Coroutines`
* `JVM`
* Update `launchSynchronously` signature
* `Selector`
* Project created
## 0.4.11 ## 0.4.11
* `Common` * `Common`

View File

@@ -17,10 +17,12 @@ You always can look at the <a href="https://github.com/InsanusMokrassar/MicroUti
## Projects ## Projects
* `common` contains common tools for platform which usually are absent out-of-the-box when you starting project * `common` contains common tools for platform which usually are absent out-of-the-box when you starting project
* `selector` contains tools to use `Selector` interface with things like `RecyclerView` in android or other selection needs
* `coroutines` is a module for `Kotlin Coroutines` with different things like subscribing on flows (`onEach` + `launchIn` shortcut :) ) * `coroutines` is a module for `Kotlin Coroutines` with different things like subscribing on flows (`onEach` + `launchIn` shortcut :) )
* `ktor` is a set of modules for `client`s and `server`s * `ktor` is a set of modules for `client`s and `server`s
* `mime_types` is NOT lightweight set of `MimeType`s with a lot of different objected and serializable (with `Kotlin Serialization`) mime types * `mime_types` is NOT lightweight set of `MimeType`s with a lot of different objected and serializable (with `Kotlin Serialization`) mime types
* `pagination` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for lightweight pagination * `pagination` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for lightweight pagination
* `serialization` is a collection of projects with serializers for `kotlinx.serialization`
* `repos` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for `KeyValue`/`OneToMany`/`CRUD` repos created to be able to exclude some heavy dependencies when you need some simple and lightweight typical repositories * `repos` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for `KeyValue`/`OneToMany`/`CRUD` repos created to be able to exclude some heavy dependencies when you need some simple and lightweight typical repositories
## Complex modules structure ## Complex modules structure

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

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.common
import org.khronos.webgl.*
fun DataView.toByteArray() = ByteArray(this.byteLength) {
getInt8(it)
}
fun ArrayBuffer.toByteArray() = Int8Array(this) as ByteArray
fun ByteArray.toDataView() = DataView(ArrayBuffer(size)).also {
forEachIndexed { i, byte -> it.setInt8(i, byte) }
}
fun ByteArray.toArrayBuffer() = toDataView().buffer

View File

@@ -5,23 +5,44 @@ import android.view.ViewGroup
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.Transformation import android.view.animation.Transformation
@PreviewFeature private fun View.performExpand(
fun View.expand(
duration: Long = 500, duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT, targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT,
onMeasured: View.() -> Unit,
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
) { ) {
measure(targetWidth, targetHeight) measure(targetWidth, targetHeight)
val measuredHeight: Int = measuredHeight onMeasured()
layoutParams.height = 0 show()
visibility = View.VISIBLE
val a: Animation = object : Animation() { val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) { override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
super.applyTransformation(interpolatedTime, t) super.applyTransformation(interpolatedTime, t)
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt() onPerformAnimation(interpolatedTime, t)
requestLayout() requestLayout()
} }
override fun willChangeBounds(): Boolean = true
}
a.duration = duration
startAnimation(a)
}
private fun View.performCollapse(
duration: Long = 500,
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
) {
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
gone()
} else {
onPerformAnimation(interpolatedTime, t)
requestLayout()
}
}
override fun willChangeBounds(): Boolean { override fun willChangeBounds(): Boolean {
return true return true
} }
@@ -32,27 +53,58 @@ fun View.expand(
startAnimation(a) startAnimation(a)
} }
@PreviewFeature
fun View.expand(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
var measuredHeight = 0
performExpand(
duration,
targetWidth,
targetHeight,
{
measuredHeight = this.measuredHeight
}
) { interpolatedTime, _ ->
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
}
}
@PreviewFeature
fun View.expandHorizontally(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
var measuredWidth = 0
performExpand(
duration,
targetWidth,
targetHeight,
{
measuredWidth = this.measuredWidth
}
) { interpolatedTime, _ ->
layoutParams.width = if (interpolatedTime == 1f) targetWidth else (measuredWidth * interpolatedTime).toInt()
}
}
@PreviewFeature @PreviewFeature
fun View.collapse(duration: Long = 500) { fun View.collapse(duration: Long = 500) {
val initialHeight: Int = measuredHeight val initialHeight: Int = measuredHeight
val a: Animation = object : Animation() { performCollapse(duration) { interpolatedTime, _ ->
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) { layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
if (interpolatedTime == 1f) {
visibility = View.GONE
} else {
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
}
} }
}
a.duration = duration @PreviewFeature
fun View.collapseHorizontally(duration: Long = 500) {
startAnimation(a) val initialWidth: Int = measuredWidth
performCollapse(duration) { interpolatedTime, _ ->
layoutParams.width = initialWidth - (initialWidth * interpolatedTime).toInt()
}
} }
@PreviewFeature @PreviewFeature
@@ -74,3 +126,15 @@ fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
collapse(duration) collapse(duration)
false false
} }
/**
* @return true in case of expanding
*/
@PreviewFeature
fun View.toggleExpandHorizontallyState(duration: Long = 500): Boolean = if (isCollapsed) {
expandHorizontally(duration)
true
} else {
collapseHorizontally(duration)
false
}

View File

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

View File

@@ -19,7 +19,7 @@ fun <T> CoroutineScope.actor(
inline fun <T> CoroutineScope.safeActor( inline fun <T> CoroutineScope.safeActor(
channelCapacity: Int = Channel.UNLIMITED, channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = {}, noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
crossinline block: suspend (T) -> Unit crossinline block: suspend (T) -> Unit
): Channel<T> = actor( ): Channel<T> = actor(
channelCapacity channelCapacity

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.*
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
scope.launch(SupervisorJob()) {
val scope = this
forEach {
scope.launch {
continuation.resume(it to it.await())
scope.cancel()
}
}
}
}.also {
if (cancelOnResult) {
forEach {
try {
it.cancel()
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}
}
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): T = awaitFirstWithDeferred(scope, cancelOnResult).second
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
cancelOthers: Boolean = true
): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers)

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,85 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
class DeferredAction<T, O>(
val deferred: Deferred<T>,
val callback: suspend (T) -> O
) {
suspend operator fun invoke() = callback(deferred.await())
}
class DoWithFirstBuilder<T>(
private val scope: CoroutineScope
) {
private val deferreds = mutableListOf<Deferred<T>>()
operator fun plus(block: suspend CoroutineScope.() -> T) {
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
}
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
fun build() = deferreds.toList()
}
fun <T, O> Deferred<T>.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback)
suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): O {
return map { it.deferred }.awaitFirstWithDeferred(scope, cancelOnResult).let { result ->
first { it.deferred == result.first }.invoke()
}
}
suspend fun <O> invokeFirstOf(
scope: CoroutineScope,
vararg variants: DeferredAction<*, O>,
cancelOnResult: Boolean = true
): O = variants.toList().invokeFirstOf(scope, cancelOnResult)
suspend fun <T, O> Iterable<Deferred<T>>.invokeOnFirst(
scope: CoroutineScope,
cancelOnResult: Boolean = true,
callback: suspend (T) -> O
): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult)
suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit,
callback: suspend (T) -> O
) = firstOf(
DoWithFirstBuilder<T>(this).apply(block).build(),
cancelOnResult
).let { callback(it) }
suspend fun <T, O> invokeOnFirst(
scope: CoroutineScope,
vararg variants: Deferred<T>,
cancelOnResult: Boolean = true,
callback: suspend (T) -> O
): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback)
suspend fun <T> CoroutineScope.firstOf(
variants: Iterable<Deferred<T>>,
cancelOnResult: Boolean = true
) = variants.invokeOnFirst(this, cancelOnResult) { it }
suspend fun <T> CoroutineScope.firstOf(
cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit
) = firstOf(
DoWithFirstBuilder<T>(this).apply(block).build(),
cancelOnResult
)
suspend fun <T> CoroutineScope.firstOf(
vararg variants: Deferred<T>,
cancelOnResult: Boolean = true
) = firstOf(variants.toList(), cancelOnResult)
suspend fun <T> List<Deferred<T>>.first(
scope: CoroutineScope,
cancelOnResult: Boolean = true
) = scope.firstOf(this, cancelOnResult)

View File

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

View File

@@ -1,30 +1,145 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.supervisorScope import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
typealias ExceptionHandler<T> = suspend (Throwable) -> T typealias ExceptionHandler<T> = suspend (Throwable) -> T
/**
* This instance will be used in all calls of [safely] where exception handler has not been passed
*/
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
/**
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
*/
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
try {
defaultSafelyExceptionHandler(it)
} catch (e: Throwable) {
// do nothing
}
}
/**
* 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]
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandler
*/
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
/**
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
class ContextSafelyExceptionHandler(
val handler: ExceptionHandler<Unit>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*>
get() = ContextSafelyExceptionHandlerKey
}
/**
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
* exists
*
* @see ContextSafelyExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
/**
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
* already contains [ContextSafelyExceptionHandler], [ContextSafelyExceptionHandler.handler] will be used BEFORE
* [contextExceptionHandler] in case of exception.
*
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
*/
suspend fun <T> safelyWithContextExceptionHandler(
contextExceptionHandler: ExceptionHandler<Unit>,
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
): T {
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
ContextSafelyExceptionHandler {
oldHandler(it)
contextExceptionHandler(it)
}
} ?: ContextSafelyExceptionHandler(contextExceptionHandler)
return withContext(contextSafelyExceptionHandler) {
safely(safelyExceptionHandler, block)
}
}
/** /**
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions * It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
* *
* Priorities of [ExceptionHandler]s:
*
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [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
*
* @see defaultSafelyExceptionHandler
* @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler
*/ */
suspend inline fun <T> safely( suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = { throw it }, noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T noinline block: suspend CoroutineScope.() -> T
): T { ): T {
return try { return try {
supervisorScope(block) supervisorScope(block)
} catch (e: Throwable) { } catch (e: Throwable) {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
onException(e) onException(e)
} }
} }
suspend inline fun <T> runCatchingSafely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatching {
safely(onException, block)
}
/** /**
* Shortcut for [safely] without exception handler (instead of this you will receive null as a result) * 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
* 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?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T noinline block: suspend CoroutineScope.() -> T
): T? = safely({ null }, block) ): T? = safely(onException, block)
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
): Result<T?> = runCatching {
safelyWithoutExceptions(onException, 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,31 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope ->
coroutineContext.job.invokeOnCompletion { newScope.cancel() }
}
fun CoroutineScope.weakLaunch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val scope = createWeakSubScope()
val job = scope.launch(context, start, block)
job.invokeOnCompletion { scope.cancel() }
return job
}
fun <T> CoroutineScope.weakAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val scope = createWeakSubScope()
val deferred = scope.async(context, start, block)
deferred.invokeOnCompletion { scope.cancel() }
return deferred
}

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.await
import org.khronos.webgl.Int8Array
import org.w3c.fetch.Response
import org.w3c.files.Blob
suspend fun Blob.toByteArray() = Int8Array(
Response(this).arrayBuffer().await()
) as ByteArray

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

@@ -2,18 +2,17 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
fun <T> launchSynchronously(scope: CoroutineScope = CoroutineScope(Dispatchers.Default), block: suspend CoroutineScope.() -> T): T { fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
var throwable: Throwable? = null val deferred = CompletableDeferred<T>()
var result: T? = null
val objectToSynchronize = java.lang.Object() val objectToSynchronize = java.lang.Object()
val launchCallback = { val launchCallback = {
scope.launch { launch {
safely( safely(
{ {
throwable = it deferred.completeExceptionally(it)
} }
) { ) {
result = block() deferred.complete(block())
} }
synchronized(objectToSynchronize) { synchronized(objectToSynchronize) {
objectToSynchronize.notifyAll() objectToSynchronize.notifyAll()
@@ -24,5 +23,10 @@ fun <T> launchSynchronously(scope: CoroutineScope = CoroutineScope(Dispatchers.D
launchCallback() launchCallback()
objectToSynchronize.wait() objectToSynchronize.wait()
} }
throw throwable ?: return result!! return deferred.getCompleted()
} }
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

@@ -0,0 +1,34 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class AwaitFirstTests {
private fun CoroutineScope.createTestDeferred(value: Int, wait: Long = 100000) = async(start = CoroutineStart.LAZY) { delay(wait); value }
@Test
fun testThatAwaitFirstIsWorkingCorrectly() {
val baseScope = CoroutineScope(Dispatchers.Default)
val resultDeferred = baseScope.createTestDeferred(-1, 0)
val deferreds = listOf(
baseScope.async { createTestDeferred(0) },
baseScope.async { createTestDeferred(1) },
baseScope.async { createTestDeferred(2) },
resultDeferred
)
val controlJob = baseScope.launch {
delay(1000000)
}
val result = baseScope.launchSynchronously {
val result = deferreds.awaitFirst(baseScope)
assertTrue(baseScope.isActive)
assertTrue(controlJob.isActive)
result
}
assertEquals(baseScope.launchSynchronously { resultDeferred.await() }, result)
assertTrue(deferreds.all { it == resultDeferred || it.isCancelled })
}
}

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.test.Test
import kotlin.test.assertEquals
class DoWithFirstTests {
@Test
fun testHandleOneOf() {
val scope = CoroutineScope(Dispatchers.Default)
val happenedDeferreds = mutableListOf<Int>()
val deferredWhichMustHappen = (-1).asDeferred
scope.launchSynchronously {
scope.launch {
((0 until 100).map {
DeferredAction(
scope.async { delay(10000); it },
happenedDeferreds::add
)
} + DeferredAction(deferredWhichMustHappen, happenedDeferreds::add)).invokeFirstOf(scope)
}.join()
}
assertEquals(1, happenedDeferreds.size)
assertEquals(scope.launchSynchronously { deferredWhichMustHappen.await() }, happenedDeferreds.first())
}
}

View File

@@ -0,0 +1,38 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.test.Test
class HandleSafelyCoroutineContextTest {
@Test
fun testHandleSafelyCoroutineContext() {
val scope = CoroutineScope(Dispatchers.Default)
var contextHandlerHappen = false
var localHandlerHappen = false
var defaultHandlerHappen = false
defaultSafelyExceptionHandler = {
defaultHandlerHappen = true
throw it
}
val contextHandler: ExceptionHandler<Unit> = {
contextHandlerHappen = true
}
val checkJob = scope.launch {
safelyWithContextExceptionHandler(contextHandler) {
safely(
{
localHandlerHappen = true
}
) {
error("That must happen :)")
}
println(coroutineContext)
error("That must happen too:)")
}
}
launchSynchronously { checkJob.join() }
assert(contextHandlerHappen)
assert(localHandlerHappen)
assert(defaultHandlerHappen)
}
}

View File

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

View File

@@ -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
)

30
crypto/build.gradle Normal file
View File

@@ -0,0 +1,30 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
}
}
jsMain {
dependencies {
api npm("crypto-js", "$crypto_js_version")
}
}
}
}
android {
sourceSets {
main {
java.srcDirs += [ "src/jvmMain/kotlin" ]
}
}
}

View File

@@ -0,0 +1,77 @@
package dev.inmo.micro_utils.crypto
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
typealias EncodedBase64String = String
typealias EncodedByteArray = ByteArray
fun SourceString.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
fun SourceString.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
fun SourceBytes.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
}
}
fun SourceBytes.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
fun EncodedBase64String.decodeBase64(): SourceBytes = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
fun EncodedBase64String.decodeBase64String(): SourceString = decodeBase64().decodeToString()
fun EncodedByteArray.decodeBase64(): SourceBytes {
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()
}
fun EncodedByteArray.decodeBase64String(): SourceString = decodeBase64().decodeToString()

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.crypto
/**
* This typealias shows that corresponding [ByteArray] is an raw data
*/
typealias SourceBytes = ByteArray
/**
* This typealias shows that corresponding [String] is an raw data
*/
typealias SourceString = String

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.crypto
typealias MD5 = String
expect fun SourceBytes.md5(): MD5
fun SourceString.md5(): MD5 = encodeToByteArray().md5()

View File

@@ -0,0 +1,32 @@
package dev.inmo.micro_utils.crypto
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

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.Test
import kotlin.test.assertEquals
class MD5Test {
val source = "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."
val expectedMD5 = "db89bb5ceab87f9c0fcc2ab36c189c2c"
@Test
fun testMD5() {
var i = 0
source.md5().forEach {
assertEquals(expectedMD5[i], it)
i++
}
}
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.crypto
external interface CryptoJs {
fun MD5(data: String): String
}
@JsModule("crypto-js")
@JsNonModule
external val CryptoJS: CryptoJs

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.crypto
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.crypto
import java.math.BigInteger
import java.security.MessageDigest
actual fun SourceBytes.md5(): MD5 = BigInteger(
1,
MessageDigest.getInstance("MD5").digest(this)
).toString(16)

View File

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

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,40 +5,45 @@ 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.20 kotlin_version=1.5.10
kotlin_coroutines_version=1.4.2 kotlin_coroutines_version=1.5.0
kotlin_serialisation_core_version=1.0.1 kotlin_serialisation_core_version=1.2.1
kotlin_exposed_version=0.28.1 kotlin_exposed_version=0.31.1
ktor_version=1.4.3 ktor_version=1.5.4
klockVersion=2.0.1 klockVersion=2.1.2
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12
uuidVersion=0.2.3 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
espresso_core=3.3.0 espresso_core=3.3.0
# JS NPM
crypto_js_version=4.0.0
# Dokka # Dokka
dokka_version=1.4.20 dokka_version=1.4.32
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.4.11 version=0.5.4
android_code_version=15 android_code_version=45

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

View File

@@ -25,7 +25,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
val producerScope = this@channelFlow val producerScope = this@channelFlow
do { do {
val reconnect = try { val reconnect = try {
safely ({ throw it }) { safely {
ws(correctedUrl) { ws(correctedUrl) {
for (received in incoming) { for (received in incoming) {
when (received) { when (received) {

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
} }

7
matrix/build.gradle Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
publish.kpsb Normal file
View File

@@ -0,0 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","includeGpgSigning":true,"developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}]}}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,3 +31,5 @@ kotlin {
} }
} }
} }
disableIncludingJvmCodeInAndroidPart()

View File

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

View File

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

View File

@@ -1,31 +1,13 @@
package dev.inmo.micro_utils.repos.pagination package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
block: (List<T>) -> Unit
) {
doWithPagination {
methodCaller(it).also {
block(it.results)
}.nextPageIfNotEmpty()
}
}
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
block: (List<T>) -> Unit
) = doForAll({ getByPagination(it) }, block)
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll( suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<T> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
): List<T> { ): List<T> = getAllWithNextPaging {
val resultList = mutableListOf<T>() methodCaller(this, it)
doForAll(methodCaller) {
resultList.addAll(it)
}
return resultList
} }

View File

@@ -1,31 +1,15 @@
package dev.inmo.micro_utils.repos.pagination package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
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()
}
}
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")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, Value>> { ): List<Pair<Key, Value>> = getAllWithNextPaging {
val resultList = mutableListOf<Pair<Key, Value>>() val result = methodCaller(it)
doForAll(methodCaller) { result.changeResultsUnchecked(
resultList.addAll(it) result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) }
} )
return resultList
} }

View File

@@ -1,41 +1,17 @@
package dev.inmo.micro_utils.repos.pagination package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
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()
}
}
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")
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, List<Value>>> { ): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
val resultList = mutableListOf<Pair<Key, List<Value>>>() val keysResult = methodCaller(it)
doForAll(methodCaller) { keysResult.changeResultsUnchecked(
resultList.addAll(it) keysResult.results.map { k ->
} k to getAll(k)
return resultList }
)
} }

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

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

View File

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

View File

@@ -5,32 +5,19 @@ 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
import kotlin.coroutines.resumeWithException 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( class StandardSQLHelper(
context: Context, context: Context,
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?) {}
@@ -38,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)
@@ -46,6 +42,20 @@ class StandardSQLHelper(
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block) suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
} }
fun <T> SQLiteOpenHelper.blockingWritableTransaction(block: SQLiteDatabase.() -> T): T {
return writableDatabase.blockingTransaction(block)
}
fun <T> SQLiteOpenHelper.blockingReadableTransaction(block: SQLiteDatabase.() -> T): T {
return readableDatabase.blockingTransaction(block)
}
fun <T> StandardSQLHelper.blockingWritableTransaction(block: SQLiteDatabase.() -> T): T {
return sqlOpenHelper.blockingWritableTransaction(block)
}
fun <T> StandardSQLHelper.blockingReadableTransaction(block: SQLiteDatabase.() -> T): T {
return sqlOpenHelper.blockingReadableTransaction(block)
}
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T { suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
return writableDatabase.transaction(block) return writableDatabase.transaction(block)
} }

View File

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

View File

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

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

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

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

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

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

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,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
}
}
}
}

View File

@@ -0,0 +1,168 @@
package dev.inmo.micro_utils.selector
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
/**
* Unified interface which can be used in any system which require some selection functionality
*/
interface Selector<T> {
val selectedItems: List<T>
val itemSelected: SharedFlow<T>
val itemUnselected: SharedFlow<T>
suspend fun toggleSelection(element: T)
suspend fun forceSelect(element: T)
suspend fun forceDeselect(element: T)
suspend fun clearSelection()
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> Selector<T>.contains(element: T) = selectedItems.contains(element)
@Suppress("NOTHING_TO_INLINE")
inline fun <T> Selector<T>.nothingSelected(): Boolean = selectedItems.isEmpty()
suspend inline fun <T> Selector<T>.toggleSelection(elements: List<T>) = elements.forEach { toggleSelection(it) }
suspend inline fun <T> Selector<T>.forceSelect(elements: List<T>) = elements.forEach { forceSelect(it) }
suspend inline fun <T> Selector<T>.forceDeselect(elements: List<T>) = elements.forEach { forceDeselect(it) }
suspend inline fun <T> Selector<T>.toggleSelection(firstElement: T, vararg elements: T) = toggleSelection(listOf(firstElement) + elements.toList())
suspend inline fun <T> Selector<T>.forceSelect(firstElement: T, vararg elements: T) = forceSelect(listOf(firstElement) + elements.toList())
suspend inline fun <T> Selector<T>.forceDeselect(firstElement: T, vararg elements: T) = forceDeselect(listOf(firstElement) + elements.toList())
/**
* Realization of [Selector] with one or without selected element. This realization will always have empty
* [selectedItems] when nothing selected and one element in [selectedItems] when something selected. Contains
* [selectedItem] value for simple access to currently selected item.
*
* On calling of [toggleSelection] previous selection will be erased and [itemUnselected] will emit this element.
*
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
*/
class SingleSelector<T>(
selectedItem: T? = null,
safeChanges: Boolean = true
) : Selector<T> {
var selectedItem: T? = selectedItem
private set
override val selectedItems: List<T>
get() = selectedItem ?.let { listOf(it) } ?: emptyList()
private val _itemSelected = MutableSharedFlow<T>()
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
private val _itemUnselected = MutableSharedFlow<T>()
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
private val mutex = if (safeChanges) {
Mutex()
} else {
null
}
override suspend fun forceDeselect(element: T) {
mutex ?.lock()
if (selectedItem == element) {
selectedItem = null
_itemUnselected.emit(element)
}
mutex ?.unlock()
}
override suspend fun forceSelect(element: T) {
mutex ?.lock()
if (selectedItem != element) {
selectedItem = element
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun toggleSelection(element: T) {
mutex ?.lock()
if (selectedItem == element) {
selectedItem = null
_itemUnselected.emit(element)
} else {
val previouslySelected = selectedItem
selectedItem = null
if (previouslySelected != null) {
_itemUnselected.emit(previouslySelected)
}
selectedItem = element
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun clearSelection() {
selectedItem ?.let { forceDeselect(it) }
}
}
/**
* Realization of [Selector] with multiple selected elements. On calling of [toggleSelection] this realization will select passed element OR deselect it if it is already in
* [selectedItems]
*
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
*/
class MultipleSelector<T>(
selectedItems: List<T> = emptyList(),
safeChanges: Boolean = true
) : Selector<T> {
private val _selectedItems: MutableList<T> = selectedItems.toMutableList()
override val selectedItems: List<T> = _selectedItems
private val _itemSelected = MutableSharedFlow<T>()
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
private val _itemUnselected = MutableSharedFlow<T>()
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
private val mutex = if (safeChanges) {
Mutex()
} else {
null
}
override suspend fun forceDeselect(element: T) {
mutex ?.lock()
if (_selectedItems.remove(element)) {
_itemUnselected.emit(element)
}
mutex ?.unlock()
}
override suspend fun forceSelect(element: T) {
mutex ?.lock()
if (element !in _selectedItems && _selectedItems.add(element)) {
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun toggleSelection(element: T) {
mutex ?.lock()
if (_selectedItems.remove(element)) {
_itemUnselected.emit(element)
} else {
_selectedItems.add(element)
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun clearSelection() {
mutex ?.lock()
val preSelectedItems = _selectedItems.toList()
_selectedItems.clear()
preSelectedItems.forEach { _itemUnselected.emit(it) }
mutex ?.unlock()
}
}
@Suppress("FunctionName", "NOTHING_TO_INLINE")
inline fun <T> Selector(
multiple: Boolean,
safeChanges: Boolean = true
): Selector<T> = if (multiple) {
MultipleSelector(safeChanges = safeChanges)
} else {
SingleSelector(safeChanges = safeChanges)
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.selector
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
/**
* @return Returned [SharedFlow] will emit true when [element] has been selected in [this] [Selector] and will emit
* false when this [element] was deselected
*
* @see [Selector]
* @see [Selector.itemSelected]
* @see [Selector.itemUnselected]
*/
fun <T> Selector<T>.itemSelectionFlow(element: T, scope: CoroutineScope): SharedFlow<Boolean> = MutableSharedFlow<Boolean>().apply {
itemSelected.onEach { if (it == element) emit(true) }.launchIn(scope)
itemUnselected.onEach { if (it == element) emit(false) }.launchIn(scope)
}.asSharedFlow()

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.serialization.base64
import dev.inmo.micro_utils.crypto.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Converts [ByteArray] into base64 encoded string due to serialization and decode base64 encoded string into bytes
*/
object Base64BytesToFromStringSerializer: KSerializer<ByteArray> {
override val descriptor: SerialDescriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): SourceBytes = decoder.decodeString().decodeBase64()
override fun serialize(encoder: Encoder, value: SourceBytes) = encoder.encodeString(value.encodeBase64String())
}

View File

@@ -0,0 +1,18 @@
package dev.inmo.micro_utils.serialization.base64
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
open class Base64Serializer<T>(
private val converterFrom: (T) -> String,
private val converterTo: (String) -> T,
) : KSerializer<T> {
override val descriptor: SerialDescriptor = Base64BytesToFromStringSerializer.descriptor
override fun deserialize(decoder: Decoder): T = converterTo(Base64BytesToFromStringSerializer.deserialize(decoder).decodeToString())
override fun serialize(encoder: Encoder, value: T) = Base64BytesToFromStringSerializer.serialize(encoder, converterFrom(value).encodeToByteArray())
}
object Base64StringSerializer : Base64Serializer<String>({ it }, { it })
object Base64ByteArraySerializer : Base64Serializer<ByteArray>({ it.decodeToString() }, { it.encodeToByteArray() })

View File

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

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,23 @@
package dev.inmo.micro_utils.serialization.encapsulator
import kotlinx.serialization.KSerializer
import kotlinx.serialization.encoding.Encoder
import kotlin.reflect.KClass
data class Encapsulator<T : Any>(
val klass: KClass<T>,
val serializer: KSerializer<T>
) {
fun <O> encapsulate(
value: Any,
callback: KSerializer<T>.(T) -> O
): O? = if (klass.isInstance(value)) {
callback(serializer, value as T)
} else {
null
}
}
fun <T : Any> Encapsulator<T>.tryEncode(encoder: Encoder, value: Any) = encapsulate(value) {
encoder.encodeSerializableValue(this, it)
}

View File

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

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"

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