Compare commits

...

167 Commits

Author SHA1 Message Date
fa090bf920 Update StartLauncherPlugin.kt 2022-12-25 10:29:16 +06:00
a83ee86340 improvements in startup launcher 2022-12-25 10:26:44 +06:00
ee56e9543a start 0.16.3 2022-12-17 17:27:15 +06:00
96fdff6ffd Merge pull request #212 from InsanusMokrassar/0.16.2
0.16.2
2022-12-16 13:59:47 +06:00
58b007cbb3 fill changelog 2022-12-16 13:59:24 +06:00
4f0c139889 remove redundant stop koin expect-actual 2022-12-16 11:58:18 +06:00
c584c24fce fixes and improvements 2022-12-15 15:07:10 +06:00
85e5cee154 replace createStartupPluginAndRegister 2022-12-15 14:59:07 +06:00
5af91981f1 upgrades and filling of README 2022-12-15 10:26:31 +06:00
2fe4f08059 update compose 2022-12-15 08:51:53 +06:00
83796f345a start fix startup 2022-12-14 22:26:23 +06:00
c1e21364a6 start 0.16.2 2022-12-14 21:40:18 +06:00
067d9d0d3b Merge pull request #211 from InsanusMokrassar/0.16.1
0.16.1
2022-12-09 19:58:54 +06:00
03f527d83e Update CHANGELOG.md 2022-12-09 19:46:58 +06:00
ced05a4586 improve default runCatchingSafely/safelyWithResult and add suspend variances of safe/unsafe SafeWrapper interface 2022-12-09 12:14:24 +06:00
43fe06206a add safe wrapper 2022-12-09 11:09:32 +06:00
023657558e start 0.16.1 2022-12-09 10:52:53 +06:00
9b0b726c80 Merge pull request #210 from InsanusMokrassar/0.16.0
0.16.0
2022-12-08 09:29:12 +06:00
4ee67321c4 fill changelog and update android dependencies 2022-12-08 09:26:59 +06:00
59f1f2e59b Update libs.versions.toml 2022-12-08 08:53:37 +06:00
0766d48b7c Update libs.versions.toml 2022-12-08 07:56:04 +06:00
e18903b9e9 Update gradle.properties 2022-12-08 07:54:53 +06:00
d0eecdead2 Update gradle.properties 2022-12-08 07:53:59 +06:00
cc4a83a033 Update gradle.properties 2022-12-08 07:53:08 +06:00
1cf911bbde Update build.gradle 2022-12-07 19:40:30 +06:00
36d73d5023 Update StartupLaunchingTests.kt 2022-12-07 11:39:45 +06:00
c395242e3e fixes in StartupLauncingTests 2022-12-07 10:50:51 +06:00
cd9cd7cc5d Merge pull request #208 from InsanusMokrassar/0.15.1
0.15.1
2022-12-07 09:43:48 +06:00
acbb8a0c07 complete prepare startup module 2022-12-06 13:15:08 +06:00
b9d8528599 complete kdocs for the startup module 2022-12-06 13:14:05 +06:00
4971326eca add kdocs for the config of startup 2022-12-06 13:08:37 +06:00
09d1047260 add kdocs to the startup module 2022-12-06 13:06:21 +06:00
02dbd493c2 add tests and make several replacements/improvements 2022-12-06 12:38:24 +06:00
b17931e7bd complete startup module 2022-12-05 22:31:15 +06:00
2a4570eafc Update ServerLauncher.kt 2022-12-05 21:52:36 +06:00
c9514d3a6d Create DefaultJson.kt 2022-12-05 21:47:00 +06:00
072805efc7 Update ServerLauncher.kt 2022-12-05 21:45:30 +06:00
369ff26627 initialize startup module 2022-12-05 21:19:38 +06:00
c5abbbbd2d start 0.15.1 2022-12-05 20:04:50 +06:00
d974639f1e fixes in source roots of dokka 2022-12-05 07:47:35 +06:00
26efde316b remove redundant fix of android 32 dx files from worflows 2022-12-05 07:09:31 +06:00
fafe50f80a suppress kdocs of uniupload in actual realizations 2022-12-05 07:00:31 +06:00
41504469db Revert "potential fix of dokka"
This reverts commit 31022733ac.
2022-12-05 06:57:29 +06:00
03b3ddd98b Revert "remove redundant dokka println"
This reverts commit b53cfd5504.
2022-12-05 06:57:27 +06:00
89d919f2be Merge pull request #207 from InsanusMokrassar/0.15.0
0.15.0
2022-12-04 19:26:14 +06:00
b53cfd5504 remove redundant dokka println 2022-12-04 19:03:44 +06:00
31022733ac potential fix of dokka 2022-12-04 19:00:16 +06:00
f9a8c39879 updates and fixes in mime types 2022-12-02 21:07:32 +06:00
a812c2dd2f refresh language codes 2022-12-02 20:03:29 +06:00
217e977f0d ids in crud repos 2022-12-02 13:46:06 +06:00
04c301d1ac upgrade version up to 0.15.0 2022-12-02 13:31:11 +06:00
7f0c425389 start 0.14.5 2022-12-02 13:04:44 +06:00
1ede1c423b Merge pull request #206 from InsanusMokrassar/0.14.4
0.14.4
2022-11-27 17:34:07 +06:00
7cb064896a fill changelog 2022-11-27 14:44:03 +06:00
0c5e2862ca improvements in ktor client-server files handling 2022-11-27 14:38:35 +06:00
30d4507f54 hotfix in handleUniUpload 2022-11-26 12:12:01 +06:00
b6c6d89455 Update gradle.properties 2022-11-25 10:16:31 +06:00
9d218ee534 Update UniloadMultipart.kt 2022-11-25 10:15:59 +06:00
11116d8cab Merge pull request #205 from InsanusMokrassar/0.14.3
0.14.3
2022-11-24 13:15:08 +06:00
58d754bbde handleUniUpload now is inline 2022-11-24 12:29:48 +06:00
8f25c123dd add download and downloadToTemporalFile extensions 2022-11-24 11:27:30 +06:00
76e214fc08 upfixes 2022-11-22 14:39:31 +06:00
2b5380f8d6 add server part of uniUpload, fill changelog 2022-11-22 13:38:36 +06:00
844a129094 complete universal uniupload 2022-11-22 12:57:43 +06:00
a3090cca9d add uniupload for JVM 2022-11-22 10:36:15 +06:00
b7b5159e9c Revert "Revert "start adding uniupload""
This reverts commit 0d1aae0ef7.
2022-11-22 09:51:10 +06:00
0f8bc2c950 fix in workflow build 2022-11-21 20:50:28 +06:00
69f5c49f45 small update of build workflows 2022-11-21 20:49:04 +06:00
9b308e6fb8 update workflow 2022-11-21 20:32:48 +06:00
3e3f91128b add dependency of common android repos onto jvm 2022-11-21 20:23:05 +06:00
0d1aae0ef7 Revert "start adding uniupload"
This reverts commit 4d022f0480.
2022-11-21 20:20:37 +06:00
4d022f0480 start adding uniupload 2022-11-21 13:39:11 +06:00
153e20d00e start 0.14.3 2022-11-21 12:49:04 +06:00
a9a8171dd6 add gitea repo 2022-11-16 21:35:18 +06:00
bf5c3b59b2 Merge pull request #204 from InsanusMokrassar/0.14.2
0.14.2
2022-11-15 10:02:11 +06:00
607c432bdb Update CHANGELOG.md 2022-11-15 10:01:58 +06:00
ae5c010770 Update libs.versions.toml 2022-11-15 09:54:18 +06:00
d5e432437f Update gradle.properties 2022-11-15 09:53:28 +06:00
9f99ebce01 Merge pull request #203 from InsanusMokrassar/0.14.1
0.14.1
2022-11-10 17:19:17 +06:00
64ee899b84 revert koin version change 2022-11-10 17:13:25 +06:00
e0e0c1658b revert kotlin versin version change 2022-11-10 17:11:45 +06:00
2c586f667c update version 2022-11-10 17:09:12 +06:00
64164ef6c1 update dependencies 2022-11-10 17:06:20 +06:00
22343c0731 Merge pull request #202 from InsanusMokrassar/0.14.0
0.14.0
2022-11-08 14:20:12 +06:00
f4ec1a4c60 add deprecations removing note 2022-11-08 14:19:48 +06:00
c1c33cceb1 remove deprecations 2022-11-08 14:17:45 +06:00
a3e975b2ba Update CHANGELOG.md 2022-11-08 14:13:36 +06:00
06e705a687 update klock 2022-11-08 13:12:39 +06:00
d56eb6c867 fixes 2022-11-08 12:47:59 +06:00
9cbca864e3 Update CHANGELOG.md 2022-11-08 08:00:18 +06:00
abb4378694 Update libs.versions.toml 2022-11-08 07:59:50 +06:00
0eb698d9a4 start 0.14.0 2022-11-08 07:57:31 +06:00
15ea9f2093 add documentation for KeyValue repo 2022-11-03 13:01:51 +06:00
d47aca0923 Merge pull request #201 from InsanusMokrassar/0.13.2
0.13.2
2022-10-30 22:35:07 +06:00
1ac50e9959 update several dependencies and fill changelog 2022-10-30 21:45:19 +06:00
6adfbe3a96 Update exposed and revert several dependencies updates 2022-10-24 23:52:04 +06:00
59f36e62e9 update dependencies 2022-10-22 14:25:46 +06:00
54af116009 start 0.13.2 2022-10-22 14:14:12 +06:00
38fbec8e3b Merge pull request #200 from InsanusMokrassar/0.13.1
0.13.1
2022-10-17 15:42:13 +06:00
babbfc55e4 update default of AbstractExposedWriteCRUDRepo 2022-10-17 15:31:27 +06:00
2511e18d69 AbstractExposedWriteCRUDRepo#createAndInsertId now is optional and returns nullable value 2022-10-17 14:42:53 +06:00
29658c70a0 start 0.13.1 2022-10-17 14:35:16 +06:00
96311ee43d Merge pull request #199 from InsanusMokrassar/0.13.0
0.13.0
2022-10-13 16:50:50 +06:00
bd33b09052 A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED 2022-10-11 13:24:39 +06:00
8055003b47 deprecations removing 2022-10-11 12:58:29 +06:00
1257492f85 changes in insert/update exposed methods and rewriting of read keyvalue(s) exposed repos to be extending abstract key value(s) repos 2022-10-11 12:55:25 +06:00
1107b7f4ef start 0.13.0 2022-10-11 12:07:49 +06:00
a1a1171240 start 0.12.18 2022-10-04 15:36:43 +06:00
46c02e5df1 Merge pull request #198 from InsanusMokrassar/0.12.17
0.12.17
2022-10-01 21:59:42 +06:00
2e9efc57de update dependencies 2022-10-01 21:56:47 +06:00
acecadef17 start 0.12.17 2022-10-01 21:41:34 +06:00
19394b5e69 Merge pull request #197 from InsanusMokrassar/0.12.16
0.12.16
2022-09-26 02:16:17 +06:00
de999e197f fix of setOnHierarchyChangeListenerRecursively 2022-09-26 01:10:10 +06:00
9d95687d3c FlowOnHierarchyChangeListener 2022-09-26 01:02:12 +06:00
aa9dfb4ab8 start 0.12.16 2022-09-26 00:47:32 +06:00
9c5b44efb3 Merge pull request #196 from InsanusMokrassar/0.12.15
0.12.15
2022-09-24 01:04:01 +06:00
ac587a67e6 findViewsByTag 2022-09-23 16:43:33 +06:00
59428140a8 applyDiff: Diff 2022-09-23 16:05:32 +06:00
60bdb59d71 fix version change 2022-09-23 12:41:57 +06:00
be52871de8 flows extensions 2022-09-23 12:41:07 +06:00
b7934cf357 flows extensions 2022-09-23 12:33:00 +06:00
dbfbeef90a start 0.12.15 2022-09-23 12:12:15 +06:00
00943c9cdf add docs for Diff class 2022-09-23 12:10:24 +06:00
8745c6a16a Merge pull request #195 from InsanusMokrassar/0.12.14
0.12.14
2022-09-23 00:19:12 +06:00
433ba4b58f argumentOrNull/argumentOrThrow 2022-09-22 23:39:08 +06:00
d40376e524 Update CHANGELOG.md 2022-09-22 21:51:40 +06:00
a2982f88f5 Update libs.versions.toml 2022-09-21 21:17:34 +06:00
1642f7abd9 improvements in AwaitFirst 2022-09-21 18:00:39 +06:00
a10d2184ff start 0.12.14 2022-09-21 17:53:57 +06:00
522435f096 Merge pull request #194 from InsanusMokrassar/0.12.13
make actor not async
2022-09-15 01:43:21 +06:00
79b30290c0 fix build 2022-09-15 01:19:05 +06:00
f8b8626859 make actor not async 2022-09-15 01:11:20 +06:00
b061b85a08 Merge pull request #193 from InsanusMokrassar/0.12.13
0.12.13
2022-09-14 23:27:59 +06:00
3870db1c88 fix of #155 and build 2022-09-14 22:47:57 +06:00
1be1070eb4 fix of #160 2022-09-14 22:32:00 +06:00
2696e663cf add RandomQualifier 2022-09-14 22:21:30 +06:00
1e1f7df86d create koin module 2022-09-14 22:14:20 +06:00
1d8ded8fd3 start 0.12.13 2022-09-14 21:42:29 +06:00
197825123a Merge pull request #192 from InsanusMokrassar/0.12.12
0.12.12
2022-09-13 22:08:52 +06:00
422b2e6db1 improve SkeletonAnimation 2022-09-13 22:01:02 +06:00
1973e0b5bf SkeletonAnimation 2022-09-13 01:30:36 +06:00
8258cf93a9 start 0.12.12 2022-09-12 21:03:07 +06:00
1d49bd5947 Merge pull request #191 from InsanusMokrassar/0.12.11
0.12.11
2022-09-08 23:46:55 +06:00
44317d1519 start 0.12.11 and add values in key value cache repo 2022-09-08 22:38:24 +06:00
48e08fcc69 Merge pull request #190 from InsanusMokrassar/0.12.10
0.12.10
2022-09-07 23:54:33 +06:00
1a3ce6e623 hotfix 2022-09-07 23:47:13 +06:00
fb122f3e70 0.12.10 2022-09-07 23:45:59 +06:00
7cca6039cc Merge pull request #189 from InsanusMokrassar/0.12.9
fix of cache filling without clearing of values
2022-09-07 22:16:50 +06:00
118e3dba39 fix of cache filling without clearing of values 2022-09-07 21:56:41 +06:00
87070710fa Merge pull request #188 from InsanusMokrassar/0.12.9
update korlibs
2022-09-07 21:14:26 +06:00
38499c3d4a update korlibs 2022-09-07 21:09:57 +06:00
5d754d968b Merge pull request #187 from InsanusMokrassar/0.12.9
0.12.9
2022-09-07 20:26:46 +06:00
d543d436bc Fixes in key values cache 2022-09-07 20:01:13 +06:00
12b54f99af update gradle wrapper 2022-09-07 19:49:29 +06:00
2a95d7e643 start 0.12.9 2022-09-07 19:48:59 +06:00
e3d3cacfa4 Merge pull request #186 from InsanusMokrassar/0.12.8
0.12.8
2022-09-07 00:57:17 +06:00
4b13491a0e Update CHANGELOG.md 2022-09-07 00:56:48 +06:00
85d516d1e9 Update libs.versions.toml 2022-09-06 23:44:02 +06:00
ac58b6a7e3 bodyOrNull/respondOrNoContent 2022-09-06 21:16:41 +06:00
2cc6126765 start 0.12.8 2022-09-06 21:15:01 +06:00
f94b085850 Merge pull request #185 from InsanusMokrassar/0.12.7
0.12.7
2022-09-05 18:35:56 +06:00
c9822a491b WriteCRUDCacheRepo subscribtions and changeResultsUnchecked(Pagination) 2022-09-05 15:07:33 +06:00
23b2d60295 start 0.12.7 2022-09-05 14:20:58 +06:00
f4bc9eed39 Merge pull request #184 from InsanusMokrassar/0.12.6
0.12.6
2022-08-31 04:10:33 +06:00
e310f188b0 fixed absence of image/* in known mime types 2022-08-31 03:01:06 +06:00
6c1571188c start 0.12.6 2022-08-31 02:59:29 +06:00
945d2fa284 Merge pull request #183 from InsanusMokrassar/0.12.5
0.12.5
2022-08-30 17:38:57 +06:00
132 changed files with 4932 additions and 3761 deletions

View File

@@ -9,9 +9,6 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
@@ -20,9 +17,8 @@ jobs:
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
# - name: Publish
# continue-on-error: true
# run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
# env:
# GITHUBPACKAGES_USER: ${{ github.actor }}
# GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
- name: Publish
continue-on-error: true
run: ./gradlew publishAllPublicationsToGiteaRepository
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

View File

@@ -11,9 +11,6 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build
run: ./gradlew build && ./gradlew dokkaHtml
- name: Publish KDocs

View File

@@ -1,8 +0,0 @@
job("Build and run tests") {
container(displayName = "Run gradle build", image = "openjdk:11") {
kotlinScript { api ->
// here can be your complex logic
api.gradlew("build")
}
}
}

View File

@@ -1,5 +1,229 @@
# Changelog
## 0.16.3
* `Startup`:
* `Launcher`:
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
## 0.16.2
* `Versions`:
* `Compose`: `1.2.1` -> `1.2.2`
* `Startup`:
* Module become available on `JS` target
## 0.16.1
* `Coroutines`:
* New `runCatchingSafely`/`safelyWithResult` with receivers
* `SafeWrapper`:
* Module inited
## 0.16.0
* `Versions`:
* `Ktor`: `2.1.3` -> `2.2.1`
* `Android Fragment`: `1.5.3` -> `1.5.5`
## 0.15.1
* `Startup`:
* Inited :)
* `Plugin`:
* Inited :)
* `Launcher`:
* Inited :)
## 0.15.0
* `Repos`:
* `CRUD`:
* `Common`:
* New method `ReadCRUDRepo#getIdsByPagination`
* `Android`:
* `AbstractAndroidCRUDRepo` got new abstract method `toId`
* `Exposed`:
* `CommonExposedRepo` new abstract property `asId`
* `Ktor`:
* `Client`:
* `KtorReadCRUDRepoClient` now requires `paginationIdType`
* `LanguageCodes`:
* Updates and fixes in generation
* `MimeTypes`:
* Updates and fixes in generation
## 0.14.4
* `Common`:
* `JVM`:
* New extension `downloadToTempFile`
* `Ktor`:
* `Server`:
* Small fix in `handleUniUpload`
* `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart`
* `Common`:
* New extension `downloadToTempFile`
* `Client`:
* New extensions on top of `uniUpload`
## 0.14.3
* `Common`:
* New type `Progress`
* `Ktor`:
* `Client`:
* New universal `uniUpload` extension for `HttpClient`
* `Server`:
* New universal `handleUniUpload` extension for `ApplicationCall`
* Add extensions `download` and `downloadToTemporalFile`
## 0.14.2
* `Versions`:
* `Exposed`: `0.40.1` -> `0.41.1`
## 0.14.1
* `Versions`:
* `Klock`: `3.3.1` -> `3.4.0`
* `UUID`: `0.5.0` -> `0.6.0`
## 0.14.0
**ALL DEPRECATIONS HAVE BEEN REMOVED**
* `Versions`:
* `Kotlin`: `1.7.10` -> `1.7.20`
* `Klock`: `3.3.0` -> `3.3.1`
* `Compose`: `1.2.0` -> `1.2.1`
* `Exposed`: `0.39.2` -> `0.40.1`
## 0.13.2
* `Versions`:
* `Klock`: `3.1.0` -> `3.3.0`
* `Ktor`: `2.1.2` -> `2.1.3`
## 0.13.1
* `Repos`:
* `Exposed`:
* `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
## 0.13.0
**ALL DEPRECATIONS HAVE BEEN REMOVED**
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
* `Repos`:
* `Exposed`:
* `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
* Old `update` method has been deprecated and not recommended to override anymore in realizations
* Old `insert` method now is `open` instead of `abstract` and can be omitted
* `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
* Old `update` method has been deprecated and not recommended to override anymore
* Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
## 0.12.17
* `Versions`:
* `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02`
* `Ktor`: `2.1.1` -> `2.1.2`
* `Koin`: `3.2.0` -> `3.2.2`
## 0.12.16
* `Coroutines`:
* `Android`:
* Add class `FlowOnHierarchyChangeListener`
* Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)`
## 0.12.15
* `Common`:
* `applyDiff` will return `Diff` object since this release
* `Android`:
* New functions/extensions `findViewsByTag` and `findViewsByTagInActivity`
* `Coroutines`:
* Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten`
* Add `Flow` extensions `takeNotNull` and `filterNotNull`
## 0.12.14
* `Versions`:
* `Android CoreKTX`: `1.8.0` -> `1.9.0`
* `Android AppCompat`: `1.4.2` -> `1.5.1`
* Android Compile SDK: 32 -> 33
* Android Build Tools: 32.0.0 -> 33.0.0
* `Common`:
* `Android`:
* Add `argumentOrNull`/`argumentOrThrow` delegates for fragments
* `Coroutines`:
* Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending
## 0.12.13
* `Coroutines`:
* Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160))
* `Koin`:
* Module inited :)
* `Repos`:
* `Android`:
* Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155))
## 0.12.12
* `Common`:
* `Compose`:
* `JS`:
* Add `SkeletonAnimation` stylesheet
## 0.12.11
* `Repos`:
* `Cache`:
* Override `KeyValue` cache method `values`
## 0.12.10
* `Repos`:
* `Cache`:
* Hotfix in key values `get`
## 0.12.9
* `Versions`:
* `Klock`: `3.0.0` -> `3.1.0`
* `Repos`:
* `Cache`:
* Fixes in key values cache
## 0.12.8
* `Versions`:
* `Ktor`: `2.1.0` -> `2.1.1`
* `Compose`: `1.2.0-alpha01-dev764` -> `1.2.0-alpha01-dev774`
* `Ktor`:
* `Client`:
* New extension `HttpClient#bodyOrNull` which returns `null` in case when server responded with `No Content` (204)
* `Server`:
* New extension `ApplicationCall#respondOrNoContent` which responds `No Content` (204) when passed data is null
## 0.12.7
* `Repos`:
* `Cache`:
* Force `WriteCRUDCacheRepo` to subscribe on new and updated objects of parent repo
* `Pagination`:
* New function `changeResultsUnchecked(Pagination)`
## 0.12.6
* `MimeeTypes>`:
* Fixed absence of `image/*` in known mime types
## 0.12.5
* `Repos`:

View File

@@ -16,6 +16,7 @@ kotlin {
androidMain {
dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
}
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.css.*
object SkeletonAnimation : StyleSheet() {
val skeletonKeyFrames: CSSNamedKeyframes by keyframes {
to { backgroundPosition("-20% 0") }
}
fun CSSBuilder.includeSkeletonStyle(
duration: CSSSizeValue<out CSSUnitTime> = 2.s,
timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut,
iterationCount: Int? = null,
direction: AnimationDirection = AnimationDirection.Normal,
keyFrames: CSSNamedKeyframes = skeletonKeyFrames,
hideChildren: Boolean = true,
hideText: Boolean = hideChildren
) {
backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)")
backgroundSize("200% 100%")
backgroundPosition("180% 0")
animation(keyFrames) {
duration(duration)
timingFunction(timingFunction)
iterationCount(iterationCount)
direction(direction)
}
if (hideText) {
property("color", "${Color.transparent} !important")
}
if (hideChildren) {
child(self, universal) style {
property("visibility", "hidden")
}
}
}
val skeleton by style {
includeSkeletonStyle()
}
}

View File

@@ -14,6 +14,14 @@ private inline fun <T> getObject(
/**
* Diff object which contains information about differences between two [Iterable]s
*
* See tests for more info
*
* @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
* @param added The object which appear in new collection only. Indexes here show the index in the new collection
* @param replaced Pair of old-new changes. First object has been presented in the old collection on its
* [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
* case when some value has been replaced after adds or removes in original collection the object index will be changed
*
* @see calculateDiff
*/
data class Diff<T> internal constructor(
@@ -165,7 +173,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
) = calculateDiff(source, strictComparison).let {
): Diff<T> = calculateDiff(source, strictComparison).also {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}

View File

@@ -0,0 +1,37 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class Progress private constructor(
val of1: Double
) {
val of1Float
get() = of1.toFloat()
val of100
get() = of1 * 100
val of100Float
get() = of100.toFloat()
val of100Int
get() = of100.toInt()
init {
require(of1 in rangeOfValues) {
"Progress main value should be in $rangeOfValues, but incoming value is $of1"
}
}
companion object {
val rangeOfValues = 0.0 .. 1.0
val START = Progress(rangeOfValues.start)
val COMPLETED = Progress(rangeOfValues.endInclusive)
operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
operator fun invoke(part: Number, total: Number) = Progress(
part.toDouble() / total.toDouble()
)
}
}

View File

@@ -0,0 +1,80 @@
@file:Suppress(
"RemoveRedundantCallsOfConversionMethods",
"RedundantVisibilityModifier",
)
package dev.inmo.micro_utils.common
import kotlin.Byte
import kotlin.Double
import kotlin.Float
import kotlin.Int
import kotlin.Long
import kotlin.Short
import kotlin.Suppress
public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()

View File

@@ -0,0 +1,20 @@
package dev.inmo.micro_utils.common
import java.io.File
import java.io.InputStream
import java.util.UUID
fun InputStream.downloadToTempFile(
fileName: String = UUID.randomUUID().toString(),
fileExtension: String? = ".temp",
folder: File? = null
) = File.createTempFile(
fileName,
fileExtension,
folder
).apply {
outputStream().use {
copyTo(it)
}
deleteOnExit()
}

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import android.os.Bundle
import android.os.Parcelable
import androidx.fragment.app.Fragment
import java.io.Serializable
import kotlin.reflect.KProperty
object ArgumentPropertyNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? {
val arguments = thisRef.arguments ?: return null
val key = property.name
return when (property.getter.returnType.classifier) {
// Scalars
String::class -> arguments.getString(key)
Boolean::class -> arguments.getBoolean(key)
Byte::class -> arguments.getByte(key)
Char::class -> arguments.getChar(key)
Double::class -> arguments.getDouble(key)
Float::class -> arguments.getFloat(key)
Int::class -> arguments.getInt(key)
Long::class -> arguments.getLong(key)
Short::class -> arguments.getShort(key)
// References
Bundle::class -> arguments.getBundle(key)
CharSequence::class -> arguments.getCharSequence(key)
Parcelable::class -> arguments.getParcelable(key)
// Scalar arrays
BooleanArray::class -> arguments.getBooleanArray(key)
ByteArray::class -> arguments.getByteArray(key)
CharArray::class -> arguments.getCharArray(key)
DoubleArray::class -> arguments.getDoubleArray(key)
FloatArray::class -> arguments.getFloatArray(key)
IntArray::class -> arguments.getIntArray(key)
LongArray::class -> arguments.getLongArray(key)
ShortArray::class -> arguments.getShortArray(key)
Array::class -> {
val componentType = property.returnType.classifier ?.javaClass ?.componentType!!
@Suppress("UNCHECKED_CAST") // Checked by reflection.
when {
Parcelable::class.java.isAssignableFrom(componentType) -> {
arguments.getParcelableArray(key)
}
String::class.java.isAssignableFrom(componentType) -> {
arguments.getStringArray(key)
}
CharSequence::class.java.isAssignableFrom(componentType) -> {
arguments.getCharSequenceArray(key)
}
Serializable::class.java.isAssignableFrom(componentType) -> {
arguments.getSerializable(key)
}
else -> {
val valueType = componentType.canonicalName
throw IllegalArgumentException(
"Illegal value array type $valueType for key \"$key\""
)
}
}
}
Serializable::class -> arguments.getSerializable(key)
else -> null
} as? T
}
}
object ArgumentPropertyNonNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T {
return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!!
}
}
fun argumentOrNull() = ArgumentPropertyNullableDelegate
fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate

View File

@@ -0,0 +1,61 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, tag)
}.toList()
}
fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, key, tag)
}.toList()
}
fun findViewsByTag(view: View, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.tag == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, tag))
}
return result.toList()
}
fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.getTag(key) == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, key, tag))
}
return result.toList()
}
fun Activity.findViewsByTag(tag: Any?) = rootView ?.let {
findViewsByTag(it, tag)
}
fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTag(tag: Any?) = view ?.let {
findViewsByTag(it, tag)
}
fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag)
fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag)

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
val Activity.rootView: View?
get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView

View File

@@ -1,19 +1,15 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actor(
channelCapacity: Int = Channel.UNLIMITED,
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
launch {
for (data in channel) {
block(data)
}
}
channel.consumeAsFlow().subscribe(this, block)
return channel
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null },
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
return channel
}
inline fun <T> CoroutineScope.safeActorAsync(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline markerFactory: suspend (T) -> Any? = { null },
crossinline block: suspend (T) -> Unit
): Channel<T> = actorAsync(
channelCapacity,
markerFactory
) {
safely(onException) {
block(it)
}
}

View File

@@ -6,23 +6,19 @@ 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()
}
): Pair<Deferred<T>, T> {
val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
val scope = scope.LinkedSupervisorScope()
forEach {
scope.launch {
resultDeferred.complete(it to it.await())
scope.cancel()
}
}
}.also {
if (cancelOnResult) {
forEach {
try {
it.cancel()
} catch (e: IllegalStateException) {
e.printStackTrace()
return resultDeferred.await().also {
if (cancelOnResult) {
forEach {
runCatchingSafely { it.cancel() }
}
}
}

View File

@@ -0,0 +1,39 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
import kotlin.js.JsName
import kotlin.jvm.JvmName
inline fun <T, R> Flow<Flow<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = flow {
collect {
it.collect {
emit(mapper(it))
}
}
}
@JsName("flatMapIterable")
@JvmName("flatMapIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = map {
it.asFlow()
}.flatMap(mapper)
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
@JsName("flatMapNotNullIterable")
@JvmName("flatMapNotNullIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
@JsName("flattenIterable")
@JvmName("flattenIterable")
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
fun <T> Flow<T>.filterNotNull() = takeNotNull()

View File

@@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely(
safely(onException, block)
}
suspend inline fun <T, R> T.runCatchingSafely(
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
noinline block: suspend T.() -> R
): Result<R> = runCatching {
safely(onException) { block() }
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> T.safelyWithResult(
noinline block: suspend T.() -> R
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
/**
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
* returning null at one time

View File

@@ -0,0 +1,50 @@
package dev.inmo.micro_utils.coroutines
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
/**
* [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener]
*
* @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this
* [ViewGroup] too
* @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow
* @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow
*/
class FlowOnHierarchyChangeListener(
private val recursive: Boolean = false,
private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
) : ViewGroup.OnHierarchyChangeListener {
val onChildViewAdded = _onChildViewAdded.asSharedFlow()
val onChildViewRemoved = _onChildViewRemoved.asSharedFlow()
/**
* Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also
* subscribe to [child] hierarchy changes.
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewAdded(parent: View, child: View) {
_onChildViewAdded.tryEmit(parent to child)
if (recursive && child is ViewGroup) {
child.setOnHierarchyChangeListener(this)
}
}
/**
* Just emit data into [onChildViewRemoved]
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewRemoved(parent: View, child: View) {
_onChildViewRemoved.tryEmit(parent to child)
}
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.coroutines
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
/**
* Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this].
* This extension DO NOT guarantee that recursive subscription will happen after this method call
*/
fun ViewGroup.setOnHierarchyChangeListenerRecursively(
listener: OnHierarchyChangeListener
) {
setOnHierarchyChangeListener(listener)
(0 until childCount).forEach {
(getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener)
}
}

View File

@@ -121,11 +121,11 @@ tasks.dokkaHtml {
// }
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
sourceRoots.setFrom(findSourcesWithName("jvmMain"))
}
named("androidMain") {
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
sourceRoots.setFrom(findSourcesWithName("androidMain"))
}
}
}

View File

@@ -23,6 +23,7 @@ allprojects {
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.12.5
android_code_version=144
version=0.16.3
android_code_version=171

View File

@@ -1,36 +1,42 @@
[versions]
kt = "1.7.10"
kt-serialization = "1.4.0"
kt = "1.7.20"
kt-serialization = "1.4.1"
kt-coroutines = "1.6.4"
jb-compose = "1.2.0-alpha01-dev764"
jb-exposed = "0.39.2"
jb-dokka = "1.7.10"
kslog = "0.5.4"
klock = "3.0.0"
uuid = "0.5.0"
jb-compose = "1.2.2"
jb-exposed = "0.41.1"
jb-dokka = "1.7.20"
ktor = "2.1.0"
klock = "3.4.0"
uuid = "0.6.0"
ktor = "2.2.1"
gh-release = "2.4.1"
android-gradle = "7.2.2"
koin = "3.2.2"
android-gradle = "7.3.0"
dexcount = "3.1.0"
android-coreKtx = "1.8.0"
android-coreKtx = "1.9.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.4.2"
android-appCompat = "1.5.1"
android-fragment = "1.5.5"
android-espresso = "3.4.0"
android-test = "1.1.3"
android-props-minSdk = "21"
android-props-compileSdk = "32"
android-props-buildTools = "32.0.0"
android-props-compileSdk = "33"
android-props-buildTools = "33.0.0"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
@@ -56,9 +62,11 @@ ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.re
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
@@ -67,6 +75,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }

View File

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

28
koin/build.gradle Normal file
View File

@@ -0,0 +1,28 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.koin
api libs.uuid
}
}
jvmMain {
dependencies {
api libs.kt.reflect
}
}
androidMain {
dependencies {
api libs.kt.reflect
}
}
}
}

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
/**
* Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
* not care about how :)
*/
inline fun <reified T : Any> Module.factoryWithRandomQualifier(
noinline definition: Definition<T>
) = factory(RandomQualifier(), definition)

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
inline fun <reified T : Any> Module.factory(
qualifier: String,
noinline definition: Definition<T>
) = factory(StringQualifier(qualifier), definition)

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct()
inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct()

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
inline fun <reified T : Any> Scope.getAny() = getAll<T>().first()
inline fun <reified T : Any> Koin.getAny() = getAll<T>().first()

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.koin
import com.benasher44.uuid.uuid4
import org.koin.core.qualifier.StringQualifier
fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun())

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
/**
* Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
* not care about how :)
*/
inline fun <reified T : Any> Module.singleWithRandomQualifier(
createdAtStart: Boolean = false,
noinline definition: Definition<T>
) = single(RandomQualifier(), createdAtStart, definition)

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
inline fun <reified T : Any> Module.single(
qualifier: String,
createdAtStart: Boolean = false,
noinline definition: Definition<T>
) = single(StringQualifier(qualifier), createdAtStart, definition)

View File

@@ -0,0 +1,27 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: Qualifier? = null,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: String,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
inline fun <reified T : Any> Module.singleWithBinds(
qualifier: Qualifier? = null,
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
inline fun <reified T : Any> Module.singleWithBinds(
qualifier: String,
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -0,0 +1,14 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
}

View File

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

View File

@@ -15,5 +15,9 @@ kotlin {
api libs.ktor.client
}
}
androidMain {
dependsOn jvmMain
}
}
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.ktor.client
import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
status == HttpStatusCode.OK
} ?.body<T>()

View File

@@ -1,82 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.ws
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame
import io.ktor.websocket.readBytes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
do {
val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
when (received) {
is Frame.Binary -> send(conversation(received.data))
else -> {
close()
return@ws
}
}
}
}
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(
url,
checkReconnection,
requestBuilder
) {
serialFormat.decodeDefault(deserializer, it)
}

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.ktor.client
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit

View File

@@ -1,260 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.readBytes
import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.*
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
class UnifiedRequester(
val client: HttpClient = HttpClient(),
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) {
suspend fun <ResultType> uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>,
value: T
) = serializationStrategy.encodeUrlQueryValue(
value,
serialFormat
)
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
fun <T> createStandardWebsocketFlow(
url: String,
checkReconnection: suspend (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
}
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
val defaultRequester = UnifiedRequester()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get(url).let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = serialFormat.encodeHex(
this,
value
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post(url) {
setBody(
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
)
}.let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData(
url,
formData = formData {
append(
"bytes",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, mimetype)
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
}
) {
requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
filename: String,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
filename,
inputProvider,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile.filename.string,
mppFile.inputProvider(),
resultDeserializer,
mimetype,
additionalParametersBuilder,
dataHeadersBuilder,
requestBuilder,
serialFormat
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)

View File

@@ -7,13 +7,5 @@ import io.ktor.client.HttpClient
expect suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
onUpload: OnUploadCallback = { _, _ -> }
): TemporalFileId
suspend fun UnifiedRequester.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId = client.tempUpload(
fullTempUploadDraftPath, file, onUpload
)

View File

@@ -0,0 +1,105 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
import io.ktor.client.HttpClient
import io.ktor.http.Headers
import io.ktor.utils.io.core.Input
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
data class UniUploadFileInfo(
val fileName: FileName,
val mimeType: String,
val inputAllocator: LambdaInputProvider
)
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
*
* @see dev.inmo.micro_utils.ktor.server.handleUniUpload
*/
expect suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T?
/**
* Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server
*
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
*/
suspend fun <T> HttpClient.uniUpload(
url: String,
file: MPPFile,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
additionalData + ("bytes" to file),
resultDeserializer,
headers,
stringFormat,
onUpload
)
/**
* Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server
*
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
*/
suspend fun <T> HttpClient.uniUpload(
url: String,
info: UniUploadFileInfo,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
additionalData + ("bytes" to info),
resultDeserializer,
headers,
stringFormat,
onUpload
)
/**
* Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName],
* [mimeType] and [inputAllocator]) with the server
*
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
*/
suspend fun <T> HttpClient.uniUpload(
url: String,
fileName: FileName,
mimeType: String,
inputAllocator: LambdaInputProvider,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
UniUploadFileInfo(fileName, mimeType, inputAllocator),
resultDeserializer,
additionalData,
headers,
stringFormat,
onUpload
)

View File

@@ -1,6 +1,8 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
@@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE
suspend fun tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (Long, Long) -> Unit
onUpload: OnUploadCallback
): TemporalFileId {
val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
formData.append(
"data",
@@ -25,7 +28,7 @@ suspend fun tempUpload(
val request = XMLHttpRequest()
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
onUpload(it.loaded.toLong(), it.total.toLong())
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
}
request.onload = {
if (request.status == 200.toShort()) {
@@ -48,12 +51,14 @@ suspend fun tempUpload(
}
}
return answer.await()
return answer.await().also {
subscope.cancel()
}
}
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit
onUpload: OnUploadCallback
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)

View File

@@ -0,0 +1,97 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.Progress
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import io.ktor.client.HttpClient
import io.ktor.http.Headers
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.job
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import org.khronos.webgl.Int8Array
import org.w3c.files.Blob
import org.w3c.xhr.FormData
import org.w3c.xhr.TEXT
import org.w3c.xhr.XMLHttpRequest
import org.w3c.xhr.XMLHttpRequestResponseType
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers,
stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? {
val formData = FormData()
val answer = CompletableDeferred<T?>(currentCoroutineContext().job)
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
data.forEach { (k, v) ->
when (v) {
is MPPFile -> formData.append(
k,
v
)
is UniUploadFileInfo -> formData.append(
k,
Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))),
v.fileName.name
)
else -> formData.append(
k,
stringFormat.encodeToString(v)
)
}
}
val request = XMLHttpRequest()
headers.forEach { s, strings ->
request.setRequestHeader(s, strings.joinToString())
}
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
}
request.onload = {
if (request.status == 200.toShort()) {
answer.complete(
stringFormat.decodeFromString(resultDeserializer, request.responseText)
)
} else {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
}
request.onerror = {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
request.open("POST", url, true)
request.send(formData)
answer.invokeOnCompletion {
runCatching {
if (request.readyState != XMLHttpRequest.DONE) {
request.abort()
}
}
}
return answer.await().also {
subscope.cancel()
}
}

View File

@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.streams.asInput
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
inputStream().asInput()
}
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()

View File

@@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (Long, Long) -> Unit
onUpload: OnUploadCallback
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(

View File

@@ -0,0 +1,102 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.Progress
import io.ktor.client.HttpClient
import io.ktor.client.engine.mergeHeaders
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.InputProvider
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.request.headers
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import java.io.File
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers,
stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? {
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
val formData = formData {
data.forEach { (k, v) ->
when (v) {
is File -> append(
k,
v.inputProviderSync(),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
}
)
is UniUploadFileInfo -> append(
k,
InputProvider(block = v.inputAllocator),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
}
)
else -> append(
k,
stringFormat.encodeToString(v)
)
}
}
}
val requestBuilder: HttpRequestBuilder.() -> Unit = {
headers {
appendAll(headers)
}
onUpload { bytesSentTotal, contentLength ->
onUpload(bytesSentTotal, contentLength)
}
}
val response = if (withBinary) {
submitFormWithBinaryData(
url,
formData,
block = requestBuilder
)
} else {
submitForm(
url,
Parameters.build {
formData.forEach {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}
},
block = requestBuilder
)
}
return if (response.status == HttpStatusCode.OK) {
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
} else {
null
}
}

View File

@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.ktor.common
import io.ktor.utils.io.core.Input
typealias LambdaInputProvider = () -> Input

View File

@@ -0,0 +1,23 @@
package dev.inmo.micro_utils.ktor.common
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.streams.asOutput
import java.io.File
import java.io.InputStream
import java.util.UUID
fun Input.downloadToTempFile(
fileName: String = UUID.randomUUID().toString(),
fileExtension: String? = ".temp",
folder: File? = null
) = File.createTempFile(
fileName,
fileExtension,
folder
).apply {
outputStream().use {
copyTo(it.asOutput())
}
deleteOnExit()
}

View File

@@ -0,0 +1,56 @@
package dev.inmo.micro_utils.ktor.server
import com.benasher44.uuid.uuid4
import io.ktor.http.content.PartData
import io.ktor.utils.io.copyTo
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.jvm.javaio.copyTo
import io.ktor.utils.io.streams.asOutput
import java.io.File
fun PartData.FileItem.download(target: File) {
provider().use { input ->
target.outputStream().use {
input.copyTo(it.asOutput())
}
}
}
fun PartData.FileItem.downloadToTemporalFile(): File {
val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply {
deleteOnExit()
}
download(outputFile)
return outputFile
}
fun PartData.BinaryItem.download(target: File) {
provider().use { input ->
target.outputStream().use {
input.copyTo(it.asOutput())
}
}
}
fun PartData.BinaryItem.downloadToTemporalFile(): File {
val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply {
deleteOnExit()
}
download(outputFile)
return outputFile
}
suspend fun PartData.BinaryChannelItem.download(target: File) {
val input = provider()
target.outputStream().use {
input.copyTo(it)
}
}
suspend fun PartData.BinaryChannelItem.downloadToTemporalFile(): File {
val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply {
deleteOnExit()
}
download(outputFile)
return outputFile
}

View File

@@ -1,61 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
@Deprecated("This method will be removed soon")
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
converter(it) ?.let { data ->
send(data)
}
}
}
}
}
@Deprecated("This method will be removed soon")
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol? = null,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(
suburl,
flow,
protocol,
converter = if (filter == null) {
{
serialFormat.encodeDefault(serializer, it)
}
} else {
{
if (filter(it)) {
serialFormat.encodeDefault(serializer, it)
} else {
null
}
}
}
)

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
suspend fun ApplicationCall.getParameterOrSendError(
field: String
) = parameters[field].also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun ApplicationCall.getQueryParameter(
field: String
) = request.queryParameters[field]
suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent(
data: T?
) {
if (data == null) {
respond(HttpStatusCode.NoContent)
} else {
respond(data)
}
}

View File

@@ -1,308 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes
import io.ktor.server.routing.Route
import io.ktor.server.websocket.WebSocketServerSession
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.*
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
@Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead")
class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) {
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
protocol: URLProtocol? = null,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
call.respondBytes (
serialFormat.encodeDefault(answerSerializer, answer),
serialFormatContentType
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
serialFormat.decodeDefault(
deserializer,
call.receive()
)
}
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
field: String
) = call.parameters[field].also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
field: String
) = call.request.queryParameters[field]
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
serialFormat.decodeHex(
deserializer,
it
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
companion object {
val default
get() = defaultUnifiedRouter
}
}
val defaultUnifiedRouter = UnifiedRouter()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
respondBytes (
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
standardKtorSerialFormatContentType
)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
standardKtorSerialFormat.decodeDefault(
deserializer,
receive()
)
}
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely {
val multipartData = receiveMultipart()
var resultInput: Input? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> resultInput = it.provider()
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
resultInput ?: error("Bytes has not been received")
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> {
var data: Optional<T>? = null
val resultInput = uniloadMultipart(
onFormItem,
{
if (it.name == "data") {
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
} else {
onCustomFileItem(it)
}
},
onBinaryChannelItem,
onBinaryContent
)
val completeData = data ?: error("Data has not been received")
return resultInput to (completeData.dataOrNull().let { it as T })
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
var data: Optional<T>? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.streamProvider().use {
it.copyTo(fileStream)
}
}
}
}
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
val completeData = data ?: error("Data has not been received")
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
if (it.name == "bytes") {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.streamProvider().use {
it.copyTo(fileStream)
}
}
}
} else {
onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun ApplicationCall.getParameterOrSendError(
field: String
) = parameters[field].also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun ApplicationCall.getQueryParameter(
field: String
) = request.queryParameters[field]
suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> ApplicationCall.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
standardKtorSerialFormat.decodeHex(
deserializer,
it
)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}

View File

@@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime
class TemporalFilesRoutingConfigurator(
private val subpath: String = DefaultTemporalFilesSubPath,
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
) : ApplicationRoutingConfigurator.Element {
interface TemporalFilesUtilizer {
@@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator(
override fun Route.invoke() {
post(subpath) {
unifiedRouter.apply {
val multipart = call.receiveMultipart()
val multipart = call.receiveMultipart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part ?.let {
if (it is PartData.FileItem) {
val fileId = TemporalFileId(uuid4().toString())
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream ->
it.streamProvider().use {
it.copyTo(outputStream)
}
}
deleteOnExit()
}
}
}
fileInfo ?.also { (fileId, file) ->
temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file
}
call.respondText(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest)
part = multipart.readPart()
}
part ?.let {
if (it is PartData.FileItem) {
val fileId = TemporalFileId(uuid4().toString())
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream ->
it.streamProvider().use {
it.copyTo(outputStream)
}
}
deleteOnExit()
}
}
}
fileInfo ?.also { (fileId, file) ->
temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file
}
call.respondText(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest)
}
}

View File

@@ -0,0 +1,70 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.downloadToTempFile
import io.ktor.http.content.*
import io.ktor.server.application.ApplicationCall
import io.ktor.server.request.receiveMultipart
import io.ktor.utils.io.core.*
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
/**
* Server-side part which receives [dev.inmo.micro_utils.ktor.client.uniUpload] request
*/
suspend inline fun ApplicationCall.handleUniUpload(
onFormItem: (PartData.FormItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onFileItem: (PartData.FileItem) -> Unit = {}
) {
val multipartData = receiveMultipart()
while (currentCoroutineContext().isActive) {
val partData = multipartData.readPart() ?: break
when (partData) {
is PartData.FormItem -> onFormItem(partData)
is PartData.FileItem -> onFileItem(partData)
is PartData.BinaryItem -> onBinaryContent(partData)
is PartData.BinaryChannelItem -> onBinaryChannelItem(partData)
}
partData.dispose()
}
}
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Input = safely {
var resultInput: Input? = null
handleUniUpload(
onFormItem,
onBinaryContent,
onBinaryChannelItem
) {
when (it.name) {
"bytes" -> resultInput = it.provider()
else -> onCustomFileItem(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
): MPPFile = safely {
uniloadMultipart(
onFormItem,
onCustomFileItem,
onBinaryChannelItem,
onBinaryContent
).downloadToTempFile()
}

View File

@@ -78,12 +78,14 @@ private fun printLanguageCodeAndTags(
indents: String = " "
): String = if (tag.subtags.isEmpty()) {
"""${indents}${baseClassSerializerAnnotationName}
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }"""
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }"""
} else {
"""
${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}"
${indents} override val withoutDialect: String
${indents} get() = code
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
@@ -104,11 +106,14 @@ import kotlinx.serialization.Serializable
${baseClassSerializerAnnotationName}
sealed class $baseClassName {
abstract val code: String
abstract val withoutDialect: String
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
$baseClassSerializerAnnotationName
data class $unknownBaseClassName (override val code: String) : $baseClassName()
data class $unknownBaseClassName (override val code: String) : $baseClassName() {
override val withoutDialect: String = code.takeWhile { it != '-' }
}
override fun toString() = code
}

View File

@@ -17,10 +17,11 @@ def fix_name(category, raw_name):
result += out1
return result
# https://www.freeformatter.com/mime-types-list.html
if __name__ == '__main__':
df = pd.read_html(open('table.html', 'r'))
mimes = []
for row in df[0].iterrows():
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
mime = row[1][1]
mime_category = mime.split('/', 1)[0]
mime_name = mime.split('/', 1)[1]
@@ -37,7 +38,7 @@ if __name__ == '__main__':
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
code2 += ' KnownMimeTypes.Any,\n'
for key, group in grouped:
group_name = key.capitalize()
group_name = fix_name(group, key)
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
for mime in group:

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.mime_types
data class CustomMimeType(override val raw: String) : MimeType

View File

@@ -0,0 +1,705 @@
package dev.inmo.micro_utils.mime_types
internal val knownMimeTypes: Set<MimeType> = setOf(
KnownMimeTypes.Any,
KnownMimeTypes.Application.VndHzn3dCrossword,
KnownMimeTypes.Application.VndMseq,
KnownMimeTypes.Application.Vnd3mPostItNotes,
KnownMimeTypes.Application.Vnd3gppPicBwLarge,
KnownMimeTypes.Application.Vnd3gppPicBwSmall,
KnownMimeTypes.Application.Vnd3gppPicBwVar,
KnownMimeTypes.Application.Vnd3gpp2Tcap,
KnownMimeTypes.Application.X7zCompressed,
KnownMimeTypes.Application.XAbiword,
KnownMimeTypes.Application.XAceCompressed,
KnownMimeTypes.Application.VndAmericandynamicsAcc,
KnownMimeTypes.Application.VndAcucobol,
KnownMimeTypes.Application.VndAcucorp,
KnownMimeTypes.Application.XAuthorwareBin,
KnownMimeTypes.Application.XAuthorwareMap,
KnownMimeTypes.Application.XAuthorwareSeg,
KnownMimeTypes.Application.VndAdobeAirApplicationInstallerPackageZip,
KnownMimeTypes.Application.XShockwaveFlash,
KnownMimeTypes.Application.VndAdobeFxp,
KnownMimeTypes.Application.Pdf,
KnownMimeTypes.Application.VndCupsPpd,
KnownMimeTypes.Application.XDirector,
KnownMimeTypes.Application.VndAdobeXdpXml,
KnownMimeTypes.Application.VndAdobeXfdf,
KnownMimeTypes.Application.VndAheadSpace,
KnownMimeTypes.Application.VndAirzipFilesecureAzf,
KnownMimeTypes.Application.VndAirzipFilesecureAzs,
KnownMimeTypes.Application.VndAmazonEbook,
KnownMimeTypes.Application.VndAmigaAmi,
KnownMimeTypes.Application.AndrewInset,
KnownMimeTypes.Application.VndAndroidPackageArchive,
KnownMimeTypes.Application.VndAnserWebCertificateIssueInitiation,
KnownMimeTypes.Application.VndAnserWebFundsTransferInitiation,
KnownMimeTypes.Application.VndAntixGameComponent,
KnownMimeTypes.Application.XAppleDiskimage,
KnownMimeTypes.Application.VndAppleInstallerXml,
KnownMimeTypes.Application.Applixware,
KnownMimeTypes.Application.VndHheLessonPlayer,
KnownMimeTypes.Application.XFreearc,
KnownMimeTypes.Application.VndAristanetworksSwi,
KnownMimeTypes.Application.AtomcatXml,
KnownMimeTypes.Application.AtomsvcXml,
KnownMimeTypes.Application.AtomXml,
KnownMimeTypes.Application.PkixAttrCert,
KnownMimeTypes.Application.VndAudiograph,
KnownMimeTypes.Application.XBcpio,
KnownMimeTypes.Application.OctetStream,
KnownMimeTypes.Application.XBittorrent,
KnownMimeTypes.Application.VndRimCod,
KnownMimeTypes.Application.VndBlueiceMultipass,
KnownMimeTypes.Application.VndBmi,
KnownMimeTypes.Application.XSh,
KnownMimeTypes.Application.VndBusinessobjects,
KnownMimeTypes.Application.XBzip,
KnownMimeTypes.Application.XBzip2,
KnownMimeTypes.Application.XCsh,
KnownMimeTypes.Application.VndChemdrawXml,
KnownMimeTypes.Application.XCdf,
KnownMimeTypes.Application.VndContactCmsg,
KnownMimeTypes.Application.VndClaymore,
KnownMimeTypes.Application.VndClonkC4group,
KnownMimeTypes.Application.CdmiCapability,
KnownMimeTypes.Application.CdmiContainer,
KnownMimeTypes.Application.CdmiDomain,
KnownMimeTypes.Application.CdmiObject,
KnownMimeTypes.Application.CdmiQueue,
KnownMimeTypes.Application.VndCluetrustCartomobileConfig,
KnownMimeTypes.Application.VndCluetrustCartomobileConfigPkg,
KnownMimeTypes.Application.MacCompactpro,
KnownMimeTypes.Application.VndWapWmlc,
KnownMimeTypes.Application.VndXara,
KnownMimeTypes.Application.VndCosmocaller,
KnownMimeTypes.Application.XCpio,
KnownMimeTypes.Application.VndCrickClicker,
KnownMimeTypes.Application.VndCrickClickerKeyboard,
KnownMimeTypes.Application.VndCrickClickerPalette,
KnownMimeTypes.Application.VndCrickClickerTemplate,
KnownMimeTypes.Application.VndCrickClickerWordbank,
KnownMimeTypes.Application.VndCriticaltoolsWbsXml,
KnownMimeTypes.Application.VndRigCryptonote,
KnownMimeTypes.Application.CuSeeme,
KnownMimeTypes.Application.PrsCww,
KnownMimeTypes.Application.VndCurlCar,
KnownMimeTypes.Application.VndCurlPcurl,
KnownMimeTypes.Application.VndYellowriverCustomMenu,
KnownMimeTypes.Application.DsscDer,
KnownMimeTypes.Application.DsscXml,
KnownMimeTypes.Application.XDebianPackage,
KnownMimeTypes.Application.XDvi,
KnownMimeTypes.Application.VndFdsnSeed,
KnownMimeTypes.Application.XDtbookXml,
KnownMimeTypes.Application.XDtbresourceXml,
KnownMimeTypes.Application.VndDvbAit,
KnownMimeTypes.Application.VndDvbService,
KnownMimeTypes.Application.XmlDtd,
KnownMimeTypes.Application.VndDolbyMlp,
KnownMimeTypes.Application.XDoom,
KnownMimeTypes.Application.VndDpgraph,
KnownMimeTypes.Application.VndDreamfactory,
KnownMimeTypes.Application.VndDynageo,
KnownMimeTypes.Application.Ecmascript,
KnownMimeTypes.Application.VndEcowinChart,
KnownMimeTypes.Application.Exi,
KnownMimeTypes.Application.VndProteusMagazine,
KnownMimeTypes.Application.EpubZip,
KnownMimeTypes.Application.VndEnliven,
KnownMimeTypes.Application.VndIsXpr,
KnownMimeTypes.Application.VndXfdl,
KnownMimeTypes.Application.EmmaXml,
KnownMimeTypes.Application.VndEzpixAlbum,
KnownMimeTypes.Application.VndEzpixPackage,
KnownMimeTypes.Application.VndDenovoFcselayoutLink,
KnownMimeTypes.Application.VndFluxtimeClip,
KnownMimeTypes.Application.VndFdf,
KnownMimeTypes.Application.VndMif,
KnownMimeTypes.Application.VndFramemaker,
KnownMimeTypes.Application.VndFscWeblaunch,
KnownMimeTypes.Application.VndFrogansFnc,
KnownMimeTypes.Application.VndFrogansLtf,
KnownMimeTypes.Application.VndFujixeroxDdd,
KnownMimeTypes.Application.VndFujixeroxDocuworks,
KnownMimeTypes.Application.VndFujixeroxDocuworksBinder,
KnownMimeTypes.Application.VndFujitsuOasys,
KnownMimeTypes.Application.VndFujitsuOasys2,
KnownMimeTypes.Application.VndFujitsuOasys3,
KnownMimeTypes.Application.VndFujitsuOasysgp,
KnownMimeTypes.Application.VndFujitsuOasysprs,
KnownMimeTypes.Application.XFuturesplash,
KnownMimeTypes.Application.VndFuzzysheet,
KnownMimeTypes.Application.VndGmx,
KnownMimeTypes.Application.VndGenomatixTuxedo,
KnownMimeTypes.Application.VndGeogebraFile,
KnownMimeTypes.Application.VndGeogebraTool,
KnownMimeTypes.Application.VndGeometryExplorer,
KnownMimeTypes.Application.VndGeonext,
KnownMimeTypes.Application.VndGeoplan,
KnownMimeTypes.Application.VndGeospace,
KnownMimeTypes.Application.XFontGhostscript,
KnownMimeTypes.Application.XFontBdf,
KnownMimeTypes.Application.XGtar,
KnownMimeTypes.Application.XTexinfo,
KnownMimeTypes.Application.XGnumeric,
KnownMimeTypes.Application.VndGoogleEarthKmlXml,
KnownMimeTypes.Application.VndGoogleEarthKmz,
KnownMimeTypes.Application.GpxXml,
KnownMimeTypes.Application.VndGrafeq,
KnownMimeTypes.Application.VndGrooveAccount,
KnownMimeTypes.Application.VndGrooveHelp,
KnownMimeTypes.Application.VndGrooveIdentityMessage,
KnownMimeTypes.Application.VndGrooveInjector,
KnownMimeTypes.Application.VndGrooveToolMessage,
KnownMimeTypes.Application.VndGrooveToolTemplate,
KnownMimeTypes.Application.VndGrooveVcard,
KnownMimeTypes.Application.Gzip,
KnownMimeTypes.Application.VndHpHpid,
KnownMimeTypes.Application.VndHpHps,
KnownMimeTypes.Application.XHdf,
KnownMimeTypes.Application.VndHbci,
KnownMimeTypes.Application.VndHpJlyt,
KnownMimeTypes.Application.VndHpPcl,
KnownMimeTypes.Application.VndHpHpgl,
KnownMimeTypes.Application.VndYamahaHvScript,
KnownMimeTypes.Application.VndYamahaHvDic,
KnownMimeTypes.Application.VndYamahaHvVoice,
KnownMimeTypes.Application.VndHydrostatixSofData,
KnownMimeTypes.Application.Hyperstudio,
KnownMimeTypes.Application.VndHalXml,
KnownMimeTypes.Application.VndIbmRightsManagement,
KnownMimeTypes.Application.VndIbmSecureContainer,
KnownMimeTypes.Application.VndIccprofile,
KnownMimeTypes.Application.VndIgloader,
KnownMimeTypes.Application.VndImmervisionIvp,
KnownMimeTypes.Application.VndImmervisionIvu,
KnownMimeTypes.Application.ReginfoXml,
KnownMimeTypes.Application.VndIntergeo,
KnownMimeTypes.Application.VndCinderella,
KnownMimeTypes.Application.VndInterconFormnet,
KnownMimeTypes.Application.VndIsacFcs,
KnownMimeTypes.Application.Ipfix,
KnownMimeTypes.Application.PkixCert,
KnownMimeTypes.Application.Pkixcmp,
KnownMimeTypes.Application.PkixCrl,
KnownMimeTypes.Application.PkixPkipath,
KnownMimeTypes.Application.VndInsorsIgm,
KnownMimeTypes.Application.VndIpunpluggedRcprofile,
KnownMimeTypes.Application.VndIrepositoryPackageXml,
KnownMimeTypes.Application.JavaArchive,
KnownMimeTypes.Application.JavaVm,
KnownMimeTypes.Application.XJavaJnlpFile,
KnownMimeTypes.Application.JavaSerializedObject,
KnownMimeTypes.Application.Javascript,
KnownMimeTypes.Application.Json,
KnownMimeTypes.Application.VndJoostJodaArchive,
KnownMimeTypes.Application.LdJson,
KnownMimeTypes.Application.VndKahootz,
KnownMimeTypes.Application.VndChipnutsKaraokeMmd,
KnownMimeTypes.Application.VndKdeKarbon,
KnownMimeTypes.Application.VndKdeKchart,
KnownMimeTypes.Application.VndKdeKformula,
KnownMimeTypes.Application.VndKdeKivio,
KnownMimeTypes.Application.VndKdeKontour,
KnownMimeTypes.Application.VndKdeKpresenter,
KnownMimeTypes.Application.VndKdeKspread,
KnownMimeTypes.Application.VndKdeKword,
KnownMimeTypes.Application.VndKenameaapp,
KnownMimeTypes.Application.VndKidspiration,
KnownMimeTypes.Application.VndKinar,
KnownMimeTypes.Application.VndKodakDescriptor,
KnownMimeTypes.Application.VndLasLasXml,
KnownMimeTypes.Application.XLatex,
KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceDesktop,
KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceExchangeXml,
KnownMimeTypes.Application.VndJam,
KnownMimeTypes.Application.VndLotus123,
KnownMimeTypes.Application.VndLotusApproach,
KnownMimeTypes.Application.VndLotusFreelance,
KnownMimeTypes.Application.VndLotusNotes,
KnownMimeTypes.Application.VndLotusOrganizer,
KnownMimeTypes.Application.VndLotusScreencam,
KnownMimeTypes.Application.VndLotusWordpro,
KnownMimeTypes.Application.MacBinhex40,
KnownMimeTypes.Application.VndMacportsPortpkg,
KnownMimeTypes.Application.VndOsgeoMapguidePackage,
KnownMimeTypes.Application.Marc,
KnownMimeTypes.Application.MarcxmlXml,
KnownMimeTypes.Application.Mxf,
KnownMimeTypes.Application.VndWolframPlayer,
KnownMimeTypes.Application.Mathematica,
KnownMimeTypes.Application.MathmlXml,
KnownMimeTypes.Application.Mbox,
KnownMimeTypes.Application.VndMedcalcdata,
KnownMimeTypes.Application.MediaservercontrolXml,
KnownMimeTypes.Application.VndMediastationCdkey,
KnownMimeTypes.Application.VndMfer,
KnownMimeTypes.Application.VndMfmp,
KnownMimeTypes.Application.MadsXml,
KnownMimeTypes.Application.MetsXml,
KnownMimeTypes.Application.ModsXml,
KnownMimeTypes.Application.Metalink4Xml,
KnownMimeTypes.Application.VndMcd,
KnownMimeTypes.Application.VndMicrografxFlo,
KnownMimeTypes.Application.VndMicrografxIgx,
KnownMimeTypes.Application.VndEszigno3Xml,
KnownMimeTypes.Application.XMsaccess,
KnownMimeTypes.Application.XMsdownload,
KnownMimeTypes.Application.VndMsArtgalry,
KnownMimeTypes.Application.VndMsCabCompressed,
KnownMimeTypes.Application.VndMsIms,
KnownMimeTypes.Application.XMsApplication,
KnownMimeTypes.Application.XMsclip,
KnownMimeTypes.Application.VndMsFontobject,
KnownMimeTypes.Application.VndMsExcel,
KnownMimeTypes.Application.VndMsExcelAddinMacroenabled12,
KnownMimeTypes.Application.VndMsExcelSheetBinaryMacroenabled12,
KnownMimeTypes.Application.VndMsExcelTemplateMacroenabled12,
KnownMimeTypes.Application.VndMsExcelSheetMacroenabled12,
KnownMimeTypes.Application.VndMsHtmlhelp,
KnownMimeTypes.Application.XMscardfile,
KnownMimeTypes.Application.VndMsLrm,
KnownMimeTypes.Application.XMsmediaview,
KnownMimeTypes.Application.XMsmoney,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlPresentation,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlide,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlideshow,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlTemplate,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlSheet,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlDocument,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlTemplate,
KnownMimeTypes.Application.XMsbinder,
KnownMimeTypes.Application.VndMsOfficetheme,
KnownMimeTypes.Application.Onenote,
KnownMimeTypes.Application.VndMsPowerpoint,
KnownMimeTypes.Application.VndMsPowerpointAddinMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointSlideMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointPresentationMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointSlideshowMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointTemplateMacroenabled12,
KnownMimeTypes.Application.VndMsProject,
KnownMimeTypes.Application.XMspublisher,
KnownMimeTypes.Application.XMsschedule,
KnownMimeTypes.Application.XSilverlightApp,
KnownMimeTypes.Application.VndMsPkiStl,
KnownMimeTypes.Application.VndMsPkiSeccat,
KnownMimeTypes.Application.VndVisio,
KnownMimeTypes.Application.VndVisio2013,
KnownMimeTypes.Application.XMsWmd,
KnownMimeTypes.Application.VndMsWpl,
KnownMimeTypes.Application.XMsWmz,
KnownMimeTypes.Application.XMsmetafile,
KnownMimeTypes.Application.XMsterminal,
KnownMimeTypes.Application.Msword,
KnownMimeTypes.Application.VndMsWordDocumentMacroenabled12,
KnownMimeTypes.Application.VndMsWordTemplateMacroenabled12,
KnownMimeTypes.Application.XMswrite,
KnownMimeTypes.Application.VndMsWorks,
KnownMimeTypes.Application.XMsXbap,
KnownMimeTypes.Application.VndMsXpsdocument,
KnownMimeTypes.Application.VndIbmMinipay,
KnownMimeTypes.Application.VndIbmModcap,
KnownMimeTypes.Application.VndJcpJavameMidletRms,
KnownMimeTypes.Application.VndTmobileLivetv,
KnownMimeTypes.Application.XMobipocketEbook,
KnownMimeTypes.Application.VndMobiusMbk,
KnownMimeTypes.Application.VndMobiusDis,
KnownMimeTypes.Application.VndMobiusPlc,
KnownMimeTypes.Application.VndMobiusMqy,
KnownMimeTypes.Application.VndMobiusMsl,
KnownMimeTypes.Application.VndMobiusTxf,
KnownMimeTypes.Application.VndMobiusDaf,
KnownMimeTypes.Application.VndMophunCertificate,
KnownMimeTypes.Application.VndMophunApplication,
KnownMimeTypes.Application.Mp21,
KnownMimeTypes.Application.Mp4,
KnownMimeTypes.Application.VndAppleMpegurl,
KnownMimeTypes.Application.VndMusician,
KnownMimeTypes.Application.VndMuveeStyle,
KnownMimeTypes.Application.XvXml,
KnownMimeTypes.Application.VndNokiaNGageData,
KnownMimeTypes.Application.VndNokiaNGageSymbianInstall,
KnownMimeTypes.Application.XDtbncxXml,
KnownMimeTypes.Application.XNetcdf,
KnownMimeTypes.Application.VndNeurolanguageNlu,
KnownMimeTypes.Application.VndDna,
KnownMimeTypes.Application.VndNoblenetDirectory,
KnownMimeTypes.Application.VndNoblenetSealer,
KnownMimeTypes.Application.VndNoblenetWeb,
KnownMimeTypes.Application.VndNokiaRadioPreset,
KnownMimeTypes.Application.VndNokiaRadioPresets,
KnownMimeTypes.Application.VndNovadigmEdm,
KnownMimeTypes.Application.VndNovadigmEdx,
KnownMimeTypes.Application.VndNovadigmExt,
KnownMimeTypes.Application.VndFlographit,
KnownMimeTypes.Application.Oda,
KnownMimeTypes.Application.Ogg,
KnownMimeTypes.Application.VndOmaDd2Xml,
KnownMimeTypes.Application.VndOasisOpendocumentTextWeb,
KnownMimeTypes.Application.OebpsPackageXml,
KnownMimeTypes.Application.VndIntuQbo,
KnownMimeTypes.Application.VndOpenofficeorgExtension,
KnownMimeTypes.Application.VndYamahaOpenscoreformat,
KnownMimeTypes.Application.VndOasisOpendocumentChart,
KnownMimeTypes.Application.VndOasisOpendocumentChartTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentDatabase,
KnownMimeTypes.Application.VndOasisOpendocumentFormula,
KnownMimeTypes.Application.VndOasisOpendocumentFormulaTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentGraphics,
KnownMimeTypes.Application.VndOasisOpendocumentGraphicsTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentImage,
KnownMimeTypes.Application.VndOasisOpendocumentImageTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentPresentation,
KnownMimeTypes.Application.VndOasisOpendocumentPresentationTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheet,
KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheetTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentText,
KnownMimeTypes.Application.VndOasisOpendocumentTextMaster,
KnownMimeTypes.Application.VndOasisOpendocumentTextTemplate,
KnownMimeTypes.Application.VndSunXmlCalc,
KnownMimeTypes.Application.VndSunXmlCalcTemplate,
KnownMimeTypes.Application.VndSunXmlDraw,
KnownMimeTypes.Application.VndSunXmlDrawTemplate,
KnownMimeTypes.Application.VndSunXmlImpress,
KnownMimeTypes.Application.VndSunXmlImpressTemplate,
KnownMimeTypes.Application.VndSunXmlMath,
KnownMimeTypes.Application.VndSunXmlWriter,
KnownMimeTypes.Application.VndSunXmlWriterGlobal,
KnownMimeTypes.Application.VndSunXmlWriterTemplate,
KnownMimeTypes.Application.XFontOtf,
KnownMimeTypes.Application.VndYamahaOpenscoreformatOsfpvgXml,
KnownMimeTypes.Application.VndOsgiDp,
KnownMimeTypes.Application.VndPalm,
KnownMimeTypes.Application.VndPawaafile,
KnownMimeTypes.Application.VndHpPclxl,
KnownMimeTypes.Application.VndPicsel,
KnownMimeTypes.Application.PicsRules,
KnownMimeTypes.Application.XChat,
KnownMimeTypes.Application.Pkcs10,
KnownMimeTypes.Application.XPkcs12,
KnownMimeTypes.Application.Pkcs7Mime,
KnownMimeTypes.Application.Pkcs7Signature,
KnownMimeTypes.Application.XPkcs7Certreqresp,
KnownMimeTypes.Application.XPkcs7Certificates,
KnownMimeTypes.Application.Pkcs8,
KnownMimeTypes.Application.VndPocketlearn,
KnownMimeTypes.Application.XFontPcf,
KnownMimeTypes.Application.FontTdpfr,
KnownMimeTypes.Application.XChessPgn,
KnownMimeTypes.Application.PskcXml,
KnownMimeTypes.Application.VndCtcPosml,
KnownMimeTypes.Application.Postscript,
KnownMimeTypes.Application.XFontType1,
KnownMimeTypes.Application.VndPowerbuilder6,
KnownMimeTypes.Application.PgpEncrypted,
KnownMimeTypes.Application.PgpSignature,
KnownMimeTypes.Application.VndPreviewsystemsBox,
KnownMimeTypes.Application.VndPviPtid1,
KnownMimeTypes.Application.PlsXml,
KnownMimeTypes.Application.VndPgFormat,
KnownMimeTypes.Application.VndPgOsasli,
KnownMimeTypes.Application.XFontLinuxPsf,
KnownMimeTypes.Application.VndPublishareDeltaTree,
KnownMimeTypes.Application.VndPmiWidget,
KnownMimeTypes.Application.VndQuarkQuarkxpress,
KnownMimeTypes.Application.VndEpsonEsf,
KnownMimeTypes.Application.VndEpsonMsf,
KnownMimeTypes.Application.VndEpsonSsf,
KnownMimeTypes.Application.VndEpsonQuickanime,
KnownMimeTypes.Application.VndIntuQfx,
KnownMimeTypes.Application.XRarCompressed,
KnownMimeTypes.Application.RsdXml,
KnownMimeTypes.Application.VndRnRealmedia,
KnownMimeTypes.Application.VndRealvncBed,
KnownMimeTypes.Application.VndRecordareMusicxml,
KnownMimeTypes.Application.VndRecordareMusicxmlXml,
KnownMimeTypes.Application.RelaxNgCompactSyntax,
KnownMimeTypes.Application.VndDataVisionRdz,
KnownMimeTypes.Application.RdfXml,
KnownMimeTypes.Application.VndCloantoRp9,
KnownMimeTypes.Application.VndJisp,
KnownMimeTypes.Application.Rtf,
KnownMimeTypes.Application.VndRoute66Link66Xml,
KnownMimeTypes.Application.RssXml,
KnownMimeTypes.Application.ShfXml,
KnownMimeTypes.Application.VndSailingtrackerTrack,
KnownMimeTypes.Application.VndSusCalendar,
KnownMimeTypes.Application.SruXml,
KnownMimeTypes.Application.SetPaymentInitiation,
KnownMimeTypes.Application.SetRegistrationInitiation,
KnownMimeTypes.Application.VndSema,
KnownMimeTypes.Application.VndSemd,
KnownMimeTypes.Application.VndSemf,
KnownMimeTypes.Application.VndSeemail,
KnownMimeTypes.Application.XFontSnf,
KnownMimeTypes.Application.ScvpVpRequest,
KnownMimeTypes.Application.ScvpVpResponse,
KnownMimeTypes.Application.ScvpCvRequest,
KnownMimeTypes.Application.ScvpCvResponse,
KnownMimeTypes.Application.Sdp,
KnownMimeTypes.Application.VndShanaInformedFormdata,
KnownMimeTypes.Application.VndShanaInformedFormtemplate,
KnownMimeTypes.Application.VndShanaInformedInterchange,
KnownMimeTypes.Application.VndShanaInformedPackage,
KnownMimeTypes.Application.ThraudXml,
KnownMimeTypes.Application.XShar,
KnownMimeTypes.Application.VndEpsonSalt,
KnownMimeTypes.Application.VndAccpacSimplyAso,
KnownMimeTypes.Application.VndAccpacSimplyImp,
KnownMimeTypes.Application.VndSimtechMindmapper,
KnownMimeTypes.Application.VndCommonspace,
KnownMimeTypes.Application.VndYamahaSmafAudio,
KnownMimeTypes.Application.VndSmaf,
KnownMimeTypes.Application.VndYamahaSmafPhrase,
KnownMimeTypes.Application.VndSmartTeacher,
KnownMimeTypes.Application.VndSvd,
KnownMimeTypes.Application.SparqlQuery,
KnownMimeTypes.Application.SparqlResultsXml,
KnownMimeTypes.Application.Srgs,
KnownMimeTypes.Application.SrgsXml,
KnownMimeTypes.Application.SsmlXml,
KnownMimeTypes.Application.VndKoan,
KnownMimeTypes.Application.VndStardivisionCalc,
KnownMimeTypes.Application.VndStardivisionDraw,
KnownMimeTypes.Application.VndStardivisionImpress,
KnownMimeTypes.Application.VndStardivisionMath,
KnownMimeTypes.Application.VndStardivisionWriter,
KnownMimeTypes.Application.VndStardivisionWriterGlobal,
KnownMimeTypes.Application.VndStepmaniaStepchart,
KnownMimeTypes.Application.XStuffit,
KnownMimeTypes.Application.XStuffitx,
KnownMimeTypes.Application.VndSolentSdkmXml,
KnownMimeTypes.Application.VndOlpcSugar,
KnownMimeTypes.Application.VndWqd,
KnownMimeTypes.Application.VndSymbianInstall,
KnownMimeTypes.Application.SmilXml,
KnownMimeTypes.Application.VndSyncmlXml,
KnownMimeTypes.Application.VndSyncmlDmWbxml,
KnownMimeTypes.Application.VndSyncmlDmXml,
KnownMimeTypes.Application.XSv4cpio,
KnownMimeTypes.Application.XSv4crc,
KnownMimeTypes.Application.SbmlXml,
KnownMimeTypes.Application.VndTaoIntentModuleArchive,
KnownMimeTypes.Application.XTar,
KnownMimeTypes.Application.XTcl,
KnownMimeTypes.Application.XTex,
KnownMimeTypes.Application.XTexTfm,
KnownMimeTypes.Application.TeiXml,
KnownMimeTypes.Application.VndSpotfireDxp,
KnownMimeTypes.Application.VndSpotfireSfs,
KnownMimeTypes.Application.TimestampedData,
KnownMimeTypes.Application.VndTridTpt,
KnownMimeTypes.Application.VndTriscapeMxs,
KnownMimeTypes.Application.VndTrueapp,
KnownMimeTypes.Application.XFontTtf,
KnownMimeTypes.Application.VndUmajin,
KnownMimeTypes.Application.VndUomlXml,
KnownMimeTypes.Application.VndUnity,
KnownMimeTypes.Application.VndUfdl,
KnownMimeTypes.Application.VndUiqTheme,
KnownMimeTypes.Application.XUstar,
KnownMimeTypes.Application.XCdlink,
KnownMimeTypes.Application.VndVsf,
KnownMimeTypes.Application.VndVcx,
KnownMimeTypes.Application.VndVisionary,
KnownMimeTypes.Application.CcxmlXml,
KnownMimeTypes.Application.VoicexmlXml,
KnownMimeTypes.Application.XWaisSource,
KnownMimeTypes.Application.VndWapWbxml,
KnownMimeTypes.Application.DavmountXml,
KnownMimeTypes.Application.XFontWoff,
KnownMimeTypes.Application.WspolicyXml,
KnownMimeTypes.Application.VndWebturbo,
KnownMimeTypes.Application.Widget,
KnownMimeTypes.Application.Winhlp,
KnownMimeTypes.Application.VndWapWmlscriptc,
KnownMimeTypes.Application.VndWordperfect,
KnownMimeTypes.Application.VndWtStf,
KnownMimeTypes.Application.WsdlXml,
KnownMimeTypes.Application.XX509CaCert,
KnownMimeTypes.Application.XXfig,
KnownMimeTypes.Application.XhtmlXml,
KnownMimeTypes.Application.Xml,
KnownMimeTypes.Application.XcapDiffXml,
KnownMimeTypes.Application.XencXml,
KnownMimeTypes.Application.PatchOpsErrorXml,
KnownMimeTypes.Application.ResourceListsXml,
KnownMimeTypes.Application.RlsServicesXml,
KnownMimeTypes.Application.ResourceListsDiffXml,
KnownMimeTypes.Application.XsltXml,
KnownMimeTypes.Application.XopXml,
KnownMimeTypes.Application.XXpinstall,
KnownMimeTypes.Application.XspfXml,
KnownMimeTypes.Application.VndMozillaXulXml,
KnownMimeTypes.Application.Yang,
KnownMimeTypes.Application.YinXml,
KnownMimeTypes.Application.VndZul,
KnownMimeTypes.Application.Zip,
KnownMimeTypes.Application.VndHandheldEntertainmentXml,
KnownMimeTypes.Application.VndZzazzDeckXml,
KnownMimeTypes.Audio.Adpcm,
KnownMimeTypes.Audio.XAac,
KnownMimeTypes.Audio.XAiff,
KnownMimeTypes.Audio.VndDeceAudio,
KnownMimeTypes.Audio.VndDigitalWinds,
KnownMimeTypes.Audio.VndDra,
KnownMimeTypes.Audio.VndDts,
KnownMimeTypes.Audio.VndDtsHd,
KnownMimeTypes.Audio.VndRip,
KnownMimeTypes.Audio.VndLucentVoice,
KnownMimeTypes.Audio.XMpegurl,
KnownMimeTypes.Audio.VndMsPlayreadyMediaPya,
KnownMimeTypes.Audio.XMsWma,
KnownMimeTypes.Audio.XMsWax,
KnownMimeTypes.Audio.Midi,
KnownMimeTypes.Audio.Mpeg,
KnownMimeTypes.Audio.Mp4,
KnownMimeTypes.Audio.VndNueraEcelp4800,
KnownMimeTypes.Audio.VndNueraEcelp7470,
KnownMimeTypes.Audio.VndNueraEcelp9600,
KnownMimeTypes.Audio.Ogg,
KnownMimeTypes.Audio.Webm,
KnownMimeTypes.Audio.Opus,
KnownMimeTypes.Audio.XPnRealaudio,
KnownMimeTypes.Audio.XPnRealaudioPlugin,
KnownMimeTypes.Audio.Basic,
KnownMimeTypes.Audio.XWav,
KnownMimeTypes.Chemical.XCdx,
KnownMimeTypes.Chemical.XCml,
KnownMimeTypes.Chemical.XCsml,
KnownMimeTypes.Chemical.XCif,
KnownMimeTypes.Chemical.XCmdf,
KnownMimeTypes.Chemical.XXyz,
KnownMimeTypes.Image.VndDxf,
KnownMimeTypes.Image.Avif,
KnownMimeTypes.Image.Bmp,
KnownMimeTypes.Image.PrsBtif,
KnownMimeTypes.Image.VndDvbSubtitle,
KnownMimeTypes.Image.XCmuRaster,
KnownMimeTypes.Image.Cgm,
KnownMimeTypes.Image.XCmx,
KnownMimeTypes.Image.VndDeceGraphic,
KnownMimeTypes.Image.VndDjvu,
KnownMimeTypes.Image.VndDwg,
KnownMimeTypes.Image.VndFujixeroxEdmicsMmr,
KnownMimeTypes.Image.VndFujixeroxEdmicsRlc,
KnownMimeTypes.Image.VndXiff,
KnownMimeTypes.Image.VndFst,
KnownMimeTypes.Image.VndFastbidsheet,
KnownMimeTypes.Image.VndFpx,
KnownMimeTypes.Image.VndNetFpx,
KnownMimeTypes.Image.XFreehand,
KnownMimeTypes.Image.G3fax,
KnownMimeTypes.Image.Gif,
KnownMimeTypes.Image.XIcon,
KnownMimeTypes.Image.Ief,
KnownMimeTypes.Image.Jpeg,
KnownMimeTypes.Image.XCitrixJpeg,
KnownMimeTypes.Image.Pjpeg,
KnownMimeTypes.Image.VndMsModi,
KnownMimeTypes.Image.Ktx,
KnownMimeTypes.Image.XPcx,
KnownMimeTypes.Image.VndAdobePhotoshop,
KnownMimeTypes.Image.XPict,
KnownMimeTypes.Image.XPortableAnymap,
KnownMimeTypes.Image.XPortableBitmap,
KnownMimeTypes.Image.XPortableGraymap,
KnownMimeTypes.Image.Png,
KnownMimeTypes.Image.XCitrixPng,
KnownMimeTypes.Image.XPng,
KnownMimeTypes.Image.XPortablePixmap,
KnownMimeTypes.Image.SvgXml,
KnownMimeTypes.Image.XRgb,
KnownMimeTypes.Image.Tiff,
KnownMimeTypes.Image.VndWapWbmp,
KnownMimeTypes.Image.Webp,
KnownMimeTypes.Image.XXbitmap,
KnownMimeTypes.Image.XXpixmap,
KnownMimeTypes.Image.XXwindowdump,
KnownMimeTypes.Message.Rfc822,
KnownMimeTypes.Model.VndDwf,
KnownMimeTypes.Model.VndColladaXml,
KnownMimeTypes.Model.VndGtw,
KnownMimeTypes.Model.VndGdl,
KnownMimeTypes.Model.Iges,
KnownMimeTypes.Model.Mesh,
KnownMimeTypes.Model.Vrml,
KnownMimeTypes.Model.VndMts,
KnownMimeTypes.Model.VndVtu,
KnownMimeTypes.Text.XAsm,
KnownMimeTypes.Text.PlainBas,
KnownMimeTypes.Text.XC,
KnownMimeTypes.Text.Css,
KnownMimeTypes.Text.Csv,
KnownMimeTypes.Text.VndCurl,
KnownMimeTypes.Text.VndCurlDcurl,
KnownMimeTypes.Text.VndCurlMcurl,
KnownMimeTypes.Text.VndCurlScurl,
KnownMimeTypes.Text.VndFmiFlexstor,
KnownMimeTypes.Text.XFortran,
KnownMimeTypes.Text.VndGraphviz,
KnownMimeTypes.Text.Html,
KnownMimeTypes.Text.Calendar,
KnownMimeTypes.Text.VndIn3d3dml,
KnownMimeTypes.Text.VndIn3dSpot,
KnownMimeTypes.Text.VndSunJ2meAppDescriptor,
KnownMimeTypes.Text.XJavaSourceJava,
KnownMimeTypes.Text.Javascript,
KnownMimeTypes.Text.VndFly,
KnownMimeTypes.Text.N3,
KnownMimeTypes.Text.XPascal,
KnownMimeTypes.Text.PrsLinesTag,
KnownMimeTypes.Text.Richtext,
KnownMimeTypes.Text.XSetext,
KnownMimeTypes.Text.Sgml,
KnownMimeTypes.Text.TabSeparatedValues,
KnownMimeTypes.Text.Plain,
KnownMimeTypes.Text.Troff,
KnownMimeTypes.Text.Turtle,
KnownMimeTypes.Text.UriList,
KnownMimeTypes.Text.XUuencode,
KnownMimeTypes.Text.XVcalendar,
KnownMimeTypes.Text.XVcard,
KnownMimeTypes.Text.VndWapWml,
KnownMimeTypes.Text.VndWapWmlscript,
KnownMimeTypes.Text.Yaml,
KnownMimeTypes.Video.V3gpp,
KnownMimeTypes.Video.V3gpp2,
KnownMimeTypes.Video.XMsvideo,
KnownMimeTypes.Video.VndDeceHd,
KnownMimeTypes.Video.VndDeceMobile,
KnownMimeTypes.Video.VndUvvuMp4,
KnownMimeTypes.Video.VndDecePd,
KnownMimeTypes.Video.VndDeceSd,
KnownMimeTypes.Video.VndDeceVideo,
KnownMimeTypes.Video.VndFvt,
KnownMimeTypes.Video.XF4v,
KnownMimeTypes.Video.XFlv,
KnownMimeTypes.Video.XFli,
KnownMimeTypes.Video.H261,
KnownMimeTypes.Video.H263,
KnownMimeTypes.Video.H264,
KnownMimeTypes.Video.Jpm,
KnownMimeTypes.Video.Jpeg,
KnownMimeTypes.Video.XM4v,
KnownMimeTypes.Video.XMsAsf,
KnownMimeTypes.Video.VndMsPlayreadyMediaPyv,
KnownMimeTypes.Video.XMsWm,
KnownMimeTypes.Video.XMsWmx,
KnownMimeTypes.Video.XMsWmv,
KnownMimeTypes.Video.XMsWvx,
KnownMimeTypes.Video.Mj2,
KnownMimeTypes.Video.Mp2t,
KnownMimeTypes.Video.VndMpegurl,
KnownMimeTypes.Video.Mpeg,
KnownMimeTypes.Video.Mp4,
KnownMimeTypes.Video.Ogg,
KnownMimeTypes.Video.Webm,
KnownMimeTypes.Video.Quicktime,
KnownMimeTypes.Video.XSgiMovie,
KnownMimeTypes.Video.VndVivo,
KnownMimeTypes.XConference.XCooltalk,
)

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.Serializable
@Serializable(MimeTypeSerializer::class)
interface MimeType {
val raw: String
}

View File

@@ -0,0 +1,24 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MimeType {
val mimeType = decoder.decodeString()
return mimeType(mimeType)
}
override fun serialize(encoder: Encoder, value: MimeType) {
encoder.encodeString(value.raw)
}
}

View File

@@ -1,10 +1,5 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
private val mimesCache = mutableMapOf<String, MimeType>().also {
knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType }
}
@@ -15,17 +10,3 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MimeType {
val mimeType = decoder.decodeString()
return mimeType(mimeType)
}
override fun serialize(encoder: Encoder, value: MimeType) {
encoder.encodeString(value.raw)
}
}

View File

@@ -0,0 +1,49 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (IR) {
browser()
nodejs()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -23,7 +23,7 @@ kotlin {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation libs.kt.serialization
api libs.kt.serialization
}
}
commonTest {

View File

@@ -23,7 +23,7 @@ kotlin {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation libs.kt.serialization
api libs.kt.serialization
implementation compose.runtime
}
}

View File

@@ -33,21 +33,17 @@ data class PaginationResult<T>(
results,
(pagesNumber * size).toLong()
)
@Deprecated("Replace with The other order of incoming parameters or objectsCount parameter")
constructor(
page: Int,
pagesNumber: Int,
results: List<T>,
size: Int
) : this(
page,
results,
pagesNumber,
size
)
}
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
fun <T> emptyPaginationResult(
basePagination: Pagination
) = PaginationResult<T>(
basePagination.page,
basePagination.size,
emptyList(),
0L
)
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality

View File

@@ -19,29 +19,29 @@ publishing {
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
}
}
}
repositories {
@@ -49,35 +49,54 @@ publishing {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
credentials {
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('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
maven {
name = "Gitea"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
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')
}
}
}
}
}
}
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)

View File

@@ -1 +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","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/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
{"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","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":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

View File

@@ -33,6 +33,14 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
kvCache.set(idGetter(it), it)
}.launchIn(scope)
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
kvCache.set(idGetter(it), it)
}.launchIn(scope)
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
kvCache.unset(it)
}.launchIn(scope)

View File

@@ -1,7 +1,6 @@
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.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
@@ -14,6 +13,16 @@ open class ReadKeyValueCacheRepo<Key,Value>(
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo {
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return keys(pagination, reversed).let {
it.changeResultsUnchecked(
it.results.mapNotNull {
get(it)
}
)
}
}
}
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(

View File

@@ -1,9 +1,7 @@
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.pagination.*
import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
@@ -15,18 +13,22 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
protected open val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
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)
return getAll(k, reversed).paginate(
pagination
)
}
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)
} ?: parentRepo.getAll(k, reversed).also {
kvCache.set(k, it)
}
}
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: (parentRepo.contains(k, v).also {
if (it) {
kvCache.unset(k) // clear as invalid
}
})
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
}
@@ -39,9 +41,21 @@ open class KeyValuesCacheRepo<Key,Value>(
kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
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)
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
kvCache.set(
k,
kvCache.get(k) ?.plus(v) ?: return@onEach
)
}.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) ->
kvCache.set(
k,
kvCache.get(k) ?.minus(v) ?: return@onEach
)
}.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
kvCache.unset(it)
}.launchIn(scope)
}
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(

View File

@@ -25,6 +25,9 @@ open class SimpleKVCache<K, V>(
kvParent.unset(it)
}
}
do {
val removed = cacheQueue.remove(k)
} while (removed)
cacheQueue.addLast(k)
kvParent.set(k, v)
}

View File

@@ -46,6 +46,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
{ if (it.results.isNotEmpty()) actualizeAll() }
)
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = doOrTakeAndActualize(
{ keys(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
{ getIdsByPagination(pagination) },
{ if (it.results.isNotEmpty()) actualizeAll() }
)
override suspend fun count(): Long = doOrTakeAndActualize(
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
{ count() },

View File

@@ -28,6 +28,7 @@ kotlin {
api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines")
}
dependsOn jvmMain
}
}
}

View File

@@ -0,0 +1,138 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
import kotlinx.coroutines.flow.Flow
/**
* Read part of [KeyValueRepo]
*
* @param Key This type will be used as key in all operations related to searches of data
* @param Value This type will be used as returning data in most "get" operations
*/
interface ReadKeyValueRepo<Key, Value> : Repo {
/**
* @return Result [Value] in case when it is presented in repo by its [k] or null otherwise
*/
suspend fun get(k: Key): Value?
/**
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
* ascending sort for [Key]s
*/
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
/**
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
* ascending sort for [Key]s
*/
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
/**
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
* ascending sort for [Key]s
*
* @param v This value should be used to exclude from search the items with different [Value]s
*/
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
/**
* @return true if [key] is presented in current collection or false otherwise
*/
suspend fun contains(key: Key): Boolean
/**
* @return count of all collection objects
*/
suspend fun count(): Long
}
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
/**
* Write part of [KeyValueRepo]
*
* @param Key This type will be used as key in all operations related to changes of data
* @param Value This type will be used as incoming data in most operations
*/
interface WriteKeyValueRepo<Key, Value> : Repo {
/**
* This flow must emit data each time when data by [Key] has been changed with [set] method or in any other way
* excluding cases of data removing
*
* @see onValueRemoved
*/
val onNewValue: Flow<Pair<Key, Value>>
/**
* This flow must emit data each time when data by [Key] has been removed with [unset]/[unsetWithValues] methods or
* in any other way
*
* @see onNewValue
*/
val onValueRemoved: Flow<Key>
/**
* Will set as batch [toSet] data in current repo. Must pass the data which were successfully updated in repo to
* [onNewValue]
*/
suspend fun set(toSet: Map<Key, Value>)
/**
* Will unset as batch data with keys from [toUnset]. Must pass the [Key]s which were successfully removed in repo to
* [onValueRemoved]
*/
suspend fun unset(toUnset: List<Key>)
/**
* Will unset as batch data with values from [toUnset]. Must pass the [Key]s which were successfully removed in repo
* to [onValueRemoved]
*/
suspend fun unsetWithValues(toUnset: List<Value>)
}
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
k: Key, v: Value
) = set(k to v)
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value
) = unsetWithValues(v.toList())
/**
* Full version of standard key-value repository with all set/unset/clear/get methods
*/
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
/**
* By default, will walk throw all the [keys] with [Value]s from [toUnset] and run [doAllWithCurrentPaging] with
* [unset] of found data [Key]s
*/
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
doAllWithCurrentPaging {
keys(v, it).also {
unset(it.results)
}
}
}
/**
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
*/
suspend fun clear() {
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
}
}
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
class DelegateBasedKeyValueRepo<Key, Value>(
readDelegate: ReadKeyValueRepo<Key, Value>,
writeDelegate: WriteKeyValueRepo<Key, Value>
) : KeyValueRepo<Key, Value>,
ReadKeyValueRepo<Key, Value> by readDelegate,
WriteKeyValueRepo<Key, Value> by writeDelegate

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType>
suspend fun getById(id: IdType): ObjectType?
suspend fun contains(id: IdType): Boolean
suspend fun count(): Long

View File

@@ -1,63 +0,0 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
import kotlinx.coroutines.flow.Flow
interface ReadKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key): Value?
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun contains(key: Key): Boolean
suspend fun count(): Long
}
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
interface WriteKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Key>
suspend fun set(toSet: Map<Key, Value>)
suspend fun unset(toUnset: List<Key>)
suspend fun unsetWithValues(toUnset: List<Value>)
}
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
k: Key, v: Value
) = set(k to v)
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value
) = unsetWithValues(v.toList())
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
doAllWithCurrentPaging {
keys(v, it).also {
unset(it.results)
}
}
}
suspend fun clear() {
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
}
}
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
class DelegateBasedKeyValueRepo<Key, Value>(
readDelegate: ReadKeyValueRepo<Key, Value>,
writeDelegate: WriteKeyValueRepo<Key, Value>
) : KeyValueRepo<Key, Value>,
ReadKeyValueRepo<Key, Value> by readDelegate,
WriteKeyValueRepo<Key, Value> by writeDelegate

View File

@@ -20,6 +20,14 @@ open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
)
}
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<FromId> = to.getIdsByPagination(
pagination
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerKey() }
)
}
override suspend fun count(): Long = to.count()
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())

View File

@@ -16,6 +16,7 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
protected abstract val tableName: String
protected abstract val idColumnName: String
protected abstract suspend fun Cursor.toObject(): ObjectType
protected abstract suspend fun Cursor.toId(): IdType
protected fun SQLiteDatabase.count(): Long = select(tableName).use {
it.count
}.toLong()
@@ -64,4 +65,23 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
}
}
}
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
return helper.readableTransaction {
select(
tableName,
limit = pagination.limitClause()
).use {
if (it.moveToFirst()) {
val resultList = mutableListOf(it.toId())
while (it.moveToNext()) {
resultList.add(it.toId())
}
resultList.createPaginationResult(pagination, count())
} else {
emptyList<IdType>().createPaginationResult(pagination, 0)
}
}
}
}
}

View File

@@ -17,7 +17,7 @@ fun <T : Any> Context.keyValueStore(
): KeyValueRepo<String, T> {
@Suppress("UNCHECKED_CAST")
return cache.getOrPut(name) {
KeyValueStore<T>(this, name, cacheValues)
KeyValueStore<T>(c = this, preferencesName = name, useCache = cacheValues)
} as KeyValueStore<T>
}
@@ -149,6 +149,14 @@ class KeyValueStore<T : Any> internal constructor (
_onValueRemovedFlow.emit(it)
}
}
companion object {
operator fun <T : Any> invoke(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)
}
}
inline fun <T : Any> SharedPreferencesKeyValueRepo(
@@ -156,3 +164,5 @@ inline fun <T : Any> SharedPreferencesKeyValueRepo(
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)
typealias KeyValueSPRepo<T> = KeyValueStore<T>

View File

@@ -22,6 +22,17 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
)
}
}
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
return transaction(db = database) {
selectAll().paginate(pagination).map {
it.asId
}.createPaginationResult(
pagination,
selectAll().count()
)
}
}
override suspend fun getById(id: IdType): ObjectType? {
return transaction(db = database) {
select {

View File

@@ -4,9 +4,9 @@ import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteCRUDRepo
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement
import org.jetbrains.exposed.sql.statements.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.Objects
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0,
@@ -27,10 +27,39 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
/**
* @param id Can be null only if [createAndInsertId] have returned null (it can be useful when you have
* autoincrement identifier)
* @param it Will be [UpdateStatement] when it is called from [update] method or [InsertStatement] from the [create]
* one. Anyway, it is main method where you should put the logic of table filling by [value] data
*
* @see createAndInsertId
*/
protected abstract fun update(id: IdType?, value: InputValueType, it: UpdateBuilder<Int>)
/**
* Override this method to interact with [it] ([InsertStatement]) and put there new id with [IdType].
*
* By default, have null value due to the fact that in the most cases users have [autoIncrement]ing id columns
*
* @return In case when id for the model has been created new [IdType] should be returned
*/
protected open fun createAndInsertId(value: InputValueType, it: InsertStatement<Number>): IdType? = null
protected open fun insert(value: InputValueType, it: InsertStatement<Number>) {
val id = createAndInsertId(value, it)
update(id, value, it as UpdateBuilder<Int>)
}
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
/**
* Use this method to do the something with [values]. You may change and output values in that list and return
* changed list of pairs
*/
protected open suspend fun onAfterCreate(
values: List<Pair<InputValueType, ObjectType>>
): List<ObjectType> = values.map { it.second }
private fun createWithoutNotification(value: InputValueType): ObjectType {
return transaction(database) {
insert { insert(value, it) }.asObject(value)
@@ -40,13 +69,18 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
onBeforeCreate(values)
return transaction(db = database) {
values.map { value -> createWithoutNotification(value) }
values.map { value -> value to createWithoutNotification(value) }
}.let {
onAfterCreate(it)
}.onEach {
_newObjectsFlow.emit(it)
}
}
protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {}
protected open suspend fun onAfterUpdate(
value: List<UpdatedValuePair<InputValueType, ObjectType>>
): List<ObjectType> = value.map { it.second }
private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? {
return transaction(db = database) {
update(
@@ -54,7 +88,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
selectById(this, id)
}
) {
update(id, value, it)
update(id, value, it as UpdateBuilder<Int>)
}
}.let {
if (it > 0) {
@@ -71,7 +105,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
onBeforeUpdate(listOf(id to value))
return updateWithoutNotification(id, value).also {
return updateWithoutNotification(id, value).let {
onAfterUpdate(listOf(value to (it ?: return@let emptyList())))
}.firstOrNull().also {
if (it != null) {
_updatedObjectsFlow.emit(it)
}
@@ -81,9 +117,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
onBeforeUpdate(values)
return (
transaction(db = database) {
values.map { (id, value) -> updateWithoutNotification(id, value) }
}.filterNotNull()
).onEach {
values.mapNotNull { (id, value) -> value to (updateWithoutNotification(id, value) ?: return@mapNotNull null) }
}
).let {
onAfterUpdate(it)
}.onEach {
_updatedObjectsFlow.emit(it)
}
}
@@ -91,9 +129,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override suspend fun deleteById(ids: List<IdType>) {
onBeforeDelete(ids)
transaction(db = database) {
val deleted = deleteWhere(null, null) {
selectByIds(ids)
}
val deleted = deleteWhere(null, null) { selectByIds(it, ids) }
if (deleted == ids.size) {
ids
} else {

View File

@@ -4,8 +4,9 @@ import org.jetbrains.exposed.sql.*
interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
val ResultRow.asObject: ObjectType
val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean>
val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
val ResultRow.asId: IdType
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
get() = { list ->
if (list.isEmpty()) {
Op.FALSE

View File

@@ -3,8 +3,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement
import org.jetbrains.exposed.sql.statements.*
import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedKeyValueRepo<Key, Value>(
@@ -20,13 +19,18 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
protected abstract fun update(k: Key, v: Value, it: UpdateStatement)
protected abstract fun insert(k: Key, v: Value, it: InsertStatement<Number>)
protected abstract fun update(k: Key, v: Value, it: UpdateBuilder<Int>)
protected abstract fun insertKey(k: Key, v: Value, it: InsertStatement<Number>)
protected open fun insert(k: Key, v: Value, it: InsertStatement<Number>) {
insertKey(k, v, it)
update(k, v, it as UpdateBuilder<Int>)
}
override suspend fun set(toSet: Map<Key, Value>) {
transaction(database) {
toSet.mapNotNull { (k, v) ->
if (update({ selectById(k) }) { update(k, v, it) } > 0) {
if (update({ selectById(k) }) { update(k, v, it as UpdateBuilder<Int>) } > 0) {
k to v
} else {
val inserted = insert {
@@ -46,9 +50,9 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
override suspend fun unset(toUnset: List<Key>) {
transaction(database) {
toUnset.mapNotNull {
if (deleteWhere { selectById(it) } > 0) {
it
toUnset.mapNotNull { item ->
if (deleteWhere { selectById(it, item) } > 0) {
item
} else {
null
}
@@ -62,7 +66,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
transaction(database) {
toUnset.flatMap {
val keys = select { selectByValue(it) }.mapNotNull { it.asKey }
deleteWhere { selectByIds(keys) }
deleteWhere { selectByIds(it, keys) }
keys
}
}.distinct().forEach {

View File

@@ -15,8 +15,14 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
CommonExposedRepo<Key, Value>,
Table(tableName ?: "") {
abstract val keyColumn: Column<*>
/**
* Same as [asId] in context of KeyValue repo
*/
abstract val ResultRow.asKey: Key
abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean>
override val ResultRow.asId: Key
get() = asKey
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
override suspend fun get(k: Key): Value? = transaction(database) {
select { selectById(k) }.limit(1).firstOrNull() ?.asObject

View File

@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
import org.jetbrains.exposed.sql.transactions.transaction
open class ExposedKeyValueRepo<Key, Value>(
@@ -47,9 +49,9 @@ open class ExposedKeyValueRepo<Key, Value>(
override suspend fun unset(toUnset: List<Key>) {
transaction(database) {
toUnset.mapNotNull {
if (deleteWhere { keyColumn.eq(it) } > 0) {
it
toUnset.mapNotNull { item ->
if (deleteWhere { keyColumn.eq(item) } > 0) {
item
} else {
null
}

View File

@@ -5,57 +5,27 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.jetbrains.exposed.sql.transactions.transaction
open class ExposedReadKeyValueRepo<Key, Value>(
override val database: Database,
database: Database,
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
val keyColumn: Column<Key> = keyColumnAllocator()
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValueRepo<Key, Value>(database, tableName) {
override val keyColumn: Column<Key> = keyColumnAllocator()
val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
override val ResultRow.asKey: Key
get() = get(keyColumn)
override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
override val ResultRow.asObject: Value
get() = get(valueColumn)
override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
override val primaryKey: Table.PrimaryKey
get() = PrimaryKey(keyColumn, valueColumn)
init { initTable() }
override suspend fun get(k: Key): Value? = transaction(database) {
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
}
override suspend fun contains(key: Key): Boolean = transaction(database) {
select { keyColumn.eq(key) }.limit(1).any()
}
override suspend fun count(): Long = transaction(database) { selectAll().count() }
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
selectAll().selectPaginated(
pagination,
keyColumn,
reversed
) {
it[keyColumn]
}
}
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
select { valueColumn.eq(v) }.selectPaginated(
pagination,
keyColumn,
reversed
) {
it[keyColumn]
}
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
selectAll().selectPaginated(
pagination,
keyColumn,
reversed
) {
it[valueColumn]
}
}
}

View File

@@ -49,7 +49,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
transaction(database) {
toRemove.keys.flatMap { k ->
toRemove[k] ?.mapNotNull { v ->
if (deleteWhere { selectById(k).and(selectByValue(v)) } > 0 ) {
if (deleteWhere { selectById(it, k).and(SqlExpressionBuilder.selectByValue(v)) } > 0 ) {
k to v
} else {
null
@@ -63,7 +63,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
override suspend fun clear(k: Key) {
transaction(database) {
deleteWhere { selectById(k) }
deleteWhere { selectById(it, k) }
}.also { _onDataCleared.emit(k) }
}
}

View File

@@ -17,7 +17,9 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
Table(tableName ?: "") {
abstract val keyColumn: Column<*>
abstract val ResultRow.asKey: Key
abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean>
override val ResultRow.asId: Key
get() = asKey
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
override suspend fun count(k: Key): Long = transaction(database) { select { selectById(k) }.count() }

View File

@@ -4,6 +4,7 @@ import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>

View File

@@ -1,10 +1,8 @@
package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.exposed.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
@@ -13,54 +11,15 @@ open class ExposedReadKeyValuesRepo<Key, Value>(
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
val keyColumn: Column<Key> = keyColumnAllocator()
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValuesRepo<Key, Value>(database, tableName) {
override val keyColumn: Column<Key> = keyColumnAllocator()
override val ResultRow.asKey: Key
get() = get(keyColumn)
override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
override val ResultRow.asObject: Value
get() = get(valueColumn)
override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
val valueColumn: Column<Value> = valueColumnAllocator()
init { initTable() }
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
override suspend fun count(): Long = transaction(database) { selectAll().count() }
override suspend fun get(
k: Key,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Value> = transaction(database) {
select { keyColumn.eq(k) }.paginate(pagination, keyColumn, reversed).map { it[valueColumn] }
}.createPaginationResult(
pagination,
count(k)
)
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
selectAll().paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
}.createPaginationResult(
pagination,
count()
)
override suspend fun keys(
v: Value,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = transaction(database) {
select { valueColumn.eq(v) }.let {
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
}
}.let { (count, list) ->
list.createPaginationResult(
pagination,
count
)
}
override suspend fun contains(k: Key): Boolean = transaction(database) {
select { keyColumn.eq(k) }.limit(1).any()
}
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
}
}

View File

@@ -15,6 +15,13 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
)
}
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
return map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult(
pagination,
count()
)
}
override suspend fun getById(id: IdType): ObjectType? = map[id]
override suspend fun contains(id: IdType): Boolean = map.containsKey(id)

View File

@@ -28,6 +28,7 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
httpClient,
typeInfo<ObjectType>(),
typeInfo<PaginationResult<ObjectType>>(),
typeInfo<PaginationResult<IdType>>(),
contentType,
idSerializer
),

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