mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 22:39:25 +00:00
Compare commits
217 Commits
Author | SHA1 | Date | |
---|---|---|---|
4704c5a33d | |||
225c06550a | |||
f0987614c6 | |||
269c2876f3 | |||
168d6acf7c | |||
a5f718e257 | |||
4f68459582 | |||
442db122cf | |||
580d757be2 | |||
47b0f6d2d8 | |||
3f6f6ebc2b | |||
2645ea29d6 | |||
79f2041565 | |||
4a7567f288 | |||
8a890ed6ed | |||
3d90df6897 | |||
681c13144a | |||
b64f2e6d32 | |||
428eabb1bd | |||
2162e83bce | |||
6142022283 | |||
e6d9c8250f | |||
46178e723b | |||
605f55acd2 | |||
0f8b69aa60 | |||
fc48446ec4 | |||
3644b83ac6 | |||
cd73791b6f | |||
03de71df2e | |||
83d5d3faf4 | |||
0c8bec4c89 | |||
7fc93817c1 | |||
d0a00031a1 | |||
6ebc5aa0c2 | |||
8a6b4bb49e | |||
20799b9a3e | |||
ec3afc615c | |||
da692ccfc3 | |||
53b89f3a18 | |||
58cded28d3 | |||
592c5f3732 | |||
f44a78a5f5 | |||
e0bdd5dfdc | |||
99c0f06b72 | |||
66fc6df3d7 | |||
a36425a905 | |||
d920fee6d4 | |||
23590be5de | |||
94acc3c93b | |||
5616326a3b | |||
7601860c5c | |||
8b43d785cc | |||
b62d3a0b7d | |||
fad73c7213 | |||
2403c7c2b0 | |||
fa090bf920 | |||
a83ee86340 | |||
204955bcce | |||
ee56e9543a | |||
96fdff6ffd | |||
58b007cbb3 | |||
4f0c139889 | |||
c584c24fce | |||
85e5cee154 | |||
5af91981f1 | |||
2fe4f08059 | |||
83796f345a | |||
c1e21364a6 | |||
067d9d0d3b | |||
03f527d83e | |||
ced05a4586 | |||
43fe06206a | |||
023657558e | |||
9b0b726c80 | |||
4ee67321c4 | |||
59f1f2e59b | |||
0766d48b7c | |||
e18903b9e9 | |||
d0eecdead2 | |||
cc4a83a033 | |||
1cf911bbde | |||
36d73d5023 | |||
c395242e3e | |||
cd9cd7cc5d | |||
acbb8a0c07 | |||
b9d8528599 | |||
4971326eca | |||
09d1047260 | |||
02dbd493c2 | |||
b17931e7bd | |||
2a4570eafc | |||
c9514d3a6d | |||
072805efc7 | |||
369ff26627 | |||
c5abbbbd2d | |||
d974639f1e | |||
26efde316b | |||
fafe50f80a | |||
41504469db | |||
03b3ddd98b | |||
89d919f2be | |||
b53cfd5504 | |||
31022733ac | |||
f9a8c39879 | |||
a812c2dd2f | |||
217e977f0d | |||
04c301d1ac | |||
7f0c425389 | |||
1ede1c423b | |||
7cb064896a | |||
0c5e2862ca | |||
30d4507f54 | |||
b6c6d89455 | |||
9d218ee534 | |||
11116d8cab | |||
58d754bbde | |||
8f25c123dd | |||
76e214fc08 | |||
2b5380f8d6 | |||
844a129094 | |||
a3090cca9d | |||
b7b5159e9c | |||
0f8bc2c950 | |||
69f5c49f45 | |||
9b308e6fb8 | |||
3e3f91128b | |||
0d1aae0ef7 | |||
4d022f0480 | |||
153e20d00e | |||
a9a8171dd6 | |||
bf5c3b59b2 | |||
607c432bdb | |||
ae5c010770 | |||
d5e432437f | |||
9f99ebce01 | |||
64ee899b84 | |||
e0e0c1658b | |||
2c586f667c | |||
64164ef6c1 | |||
22343c0731 | |||
f4ec1a4c60 | |||
c1c33cceb1 | |||
a3e975b2ba | |||
06e705a687 | |||
d56eb6c867 | |||
9cbca864e3 | |||
abb4378694 | |||
0eb698d9a4 | |||
15ea9f2093 | |||
d47aca0923 | |||
1ac50e9959 | |||
6adfbe3a96 | |||
59f36e62e9 | |||
54af116009 | |||
38fbec8e3b | |||
babbfc55e4 | |||
2511e18d69 | |||
29658c70a0 | |||
96311ee43d | |||
bd33b09052 | |||
8055003b47 | |||
1257492f85 | |||
1107b7f4ef | |||
a1a1171240 | |||
46c02e5df1 | |||
2e9efc57de | |||
acecadef17 | |||
19394b5e69 | |||
de999e197f | |||
9d95687d3c | |||
aa9dfb4ab8 | |||
9c5b44efb3 | |||
ac587a67e6 | |||
59428140a8 | |||
60bdb59d71 | |||
be52871de8 | |||
b7934cf357 | |||
dbfbeef90a | |||
00943c9cdf | |||
8745c6a16a | |||
433ba4b58f | |||
d40376e524 | |||
a2982f88f5 | |||
1642f7abd9 | |||
a10d2184ff | |||
522435f096 | |||
79b30290c0 | |||
f8b8626859 | |||
b061b85a08 | |||
3870db1c88 | |||
1be1070eb4 | |||
2696e663cf | |||
1e1f7df86d | |||
1d8ded8fd3 | |||
197825123a | |||
422b2e6db1 | |||
1973e0b5bf | |||
8258cf93a9 | |||
1d49bd5947 | |||
44317d1519 | |||
48e08fcc69 | |||
1a3ce6e623 | |||
fb122f3e70 | |||
7cca6039cc | |||
118e3dba39 | |||
87070710fa | |||
38499c3d4a | |||
5d754d968b | |||
d543d436bc | |||
12b54f99af | |||
2a95d7e643 | |||
e3d3cacfa4 | |||
4b13491a0e | |||
85d516d1e9 | |||
ac58b6a7e3 | |||
2cc6126765 | |||
f94b085850 |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -9,9 +9,6 @@ jobs:
|
|||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
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
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
@@ -20,9 +17,8 @@ jobs:
|
|||||||
mv gradle.properties.tmp gradle.properties
|
mv gradle.properties.tmp gradle.properties
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
# - name: Publish
|
- name: Publish
|
||||||
# continue-on-error: true
|
continue-on-error: true
|
||||||
# run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||||
# env:
|
env:
|
||||||
# GITHUBPACKAGES_USER: ${{ github.actor }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
# GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
3
.github/workflows/dokka_push.yml
vendored
3
.github/workflows/dokka_push.yml
vendored
@@ -11,9 +11,6 @@ jobs:
|
|||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
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
|
- name: Build
|
||||||
run: ./gradlew build && ./gradlew dokkaHtml
|
run: ./gradlew build && ./gradlew dokkaHtml
|
||||||
- name: Publish KDocs
|
- name: Publish KDocs
|
||||||
|
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
274
CHANGELOG.md
274
CHANGELOG.md
@@ -1,5 +1,279 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.16.8
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `2.2.2` -> `2.2.3`
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`
|
||||||
|
* Fixes in `HttpClient.uniUpload`
|
||||||
|
* `Server`
|
||||||
|
* Fixes in `PartData.FileItem.download`
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* New type of caches: `FallbackCacheRepo`
|
||||||
|
* Fixes in `Write*` variants of cached repos
|
||||||
|
* New type `ActionWrapper`
|
||||||
|
* New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
|
||||||
|
* `Common`:
|
||||||
|
* New transformations for key-value and key-values vice-verse
|
||||||
|
* Fixes in `FileReadKeyValueRepo`
|
||||||
|
|
||||||
|
## 0.16.7
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse`
|
||||||
|
* `Diff` now is serializable
|
||||||
|
* Add `IndexedValue` serializer
|
||||||
|
* `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something
|
||||||
|
* `Compose`:
|
||||||
|
* New extension `MutableState.asState`
|
||||||
|
* `Coroutines`:
|
||||||
|
* `Compose`:
|
||||||
|
* All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new
|
||||||
|
parameters
|
||||||
|
* `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState`
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* New type `FullCacheRepo`
|
||||||
|
* New type `CommonCacheRepo`
|
||||||
|
* `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo`
|
||||||
|
* New extensions `KVCache.actualizeAll`
|
||||||
|
|
||||||
|
## 0.16.6
|
||||||
|
|
||||||
|
* `Startup`:
|
||||||
|
* `Launcher`:
|
||||||
|
* Improvements in `StartLauncherPlugin#start` methods
|
||||||
|
* Add opportunity to pass second argument on `JVM` platform as log level
|
||||||
|
* `Repos`:
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`:
|
||||||
|
* All clients repos got opportunity to customize their flows
|
||||||
|
* `Exposed`:
|
||||||
|
* Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column`
|
||||||
|
|
||||||
|
## 0.16.5
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `2.2.1` -> `2.2.2`
|
||||||
|
|
||||||
|
## 0.16.4
|
||||||
|
|
||||||
|
* `Coroutines`:
|
||||||
|
* Create `launchInCurrentThread`
|
||||||
|
|
||||||
|
## 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
|
## 0.12.7
|
||||||
|
|
||||||
* `Repos`:
|
* `Repos`:
|
||||||
|
@@ -16,6 +16,7 @@ kotlin {
|
|||||||
androidMain {
|
androidMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":micro_utils.coroutines")
|
api project(":micro_utils.coroutines")
|
||||||
|
api libs.android.fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.inmo.micro_utils.common.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts current [MutableState] to immutable [State] using [derivedStateOf]
|
||||||
|
*/
|
||||||
|
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
private inline fun <T> getObject(
|
private inline fun <T> getObject(
|
||||||
additional: MutableList<T>,
|
additional: MutableList<T>,
|
||||||
iterator: Iterator<T>
|
iterator: Iterator<T>
|
||||||
@@ -14,15 +16,24 @@ private inline fun <T> getObject(
|
|||||||
/**
|
/**
|
||||||
* Diff object which contains information about differences between two [Iterable]s
|
* 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
|
* @see calculateDiff
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class Diff<T> internal constructor(
|
data class Diff<T> internal constructor(
|
||||||
val removed: List<IndexedValue<T>>,
|
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
|
||||||
/**
|
/**
|
||||||
* Old-New values pairs
|
* Old-New values pairs
|
||||||
*/
|
*/
|
||||||
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
|
||||||
val added: List<IndexedValue<T>>
|
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
|
||||||
)
|
)
|
||||||
|
|
||||||
private inline fun <T> performChanges(
|
private inline fun <T> performChanges(
|
||||||
@@ -165,7 +176,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
|
|||||||
fun <T> MutableList<T>.applyDiff(
|
fun <T> MutableList<T>.applyDiff(
|
||||||
source: Iterable<T>,
|
source: Iterable<T>,
|
||||||
strictComparison: Boolean = false
|
strictComparison: Boolean = false
|
||||||
) = calculateDiff(source, strictComparison).let {
|
): Diff<T> = calculateDiff(source, strictComparison).also {
|
||||||
for (i in it.removed.indices.sortedDescending()) {
|
for (i in it.removed.indices.sortedDescending()) {
|
||||||
removeAt(it.removed[i].index)
|
removeAt(it.removed[i].index)
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
|
||||||
|
return if (this) {
|
||||||
|
block()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
|
||||||
|
return if (this) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
|
||||||
|
letIfTrue(block)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
|
||||||
|
letIfFalse(block)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
|
||||||
|
return if (this) {
|
||||||
|
block()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
|
||||||
|
return if (this) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializer
|
||||||
|
import kotlinx.serialization.builtins.PairSerializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
|
||||||
|
private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = originalSerializer.descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): IndexedValue<T> {
|
||||||
|
val pair = originalSerializer.deserialize(decoder)
|
||||||
|
return IndexedValue(
|
||||||
|
pair.first,
|
||||||
|
pair.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
|
||||||
|
originalSerializer.serialize(
|
||||||
|
encoder,
|
||||||
|
Pair(value.index, value.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -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()
|
@@ -1,5 +1,27 @@
|
|||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given [action] until getting of successful result specified number of [times].
|
||||||
|
*
|
||||||
|
* A zero-based index of current iteration is passed as a parameter to [action].
|
||||||
|
*/
|
||||||
|
inline fun <R> repeatOnFailure(
|
||||||
|
onFailure: (Throwable) -> Boolean,
|
||||||
|
action: () -> R
|
||||||
|
): Result<R> {
|
||||||
|
do {
|
||||||
|
runCatching {
|
||||||
|
action()
|
||||||
|
}.onFailure {
|
||||||
|
if (!onFailure(it)) {
|
||||||
|
return Result.failure(it)
|
||||||
|
}
|
||||||
|
}.onSuccess {
|
||||||
|
return Result.success(it)
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given [action] until getting of successful result specified number of [times].
|
* Executes the given [action] until getting of successful result specified number of [times].
|
||||||
*
|
*
|
||||||
@@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure(
|
|||||||
onEachFailure: (Throwable) -> Unit = {},
|
onEachFailure: (Throwable) -> Unit = {},
|
||||||
action: (Int) -> R
|
action: (Int) -> R
|
||||||
): Optional<R> {
|
): Optional<R> {
|
||||||
repeat(times) {
|
var i = 0
|
||||||
runCatching {
|
val result = repeatOnFailure(
|
||||||
action(it)
|
{
|
||||||
}.onFailure(onEachFailure).onSuccess {
|
onEachFailure(it)
|
||||||
return Optional.presented(it)
|
if (i < times) {
|
||||||
|
i++
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
action(i)
|
||||||
|
}
|
||||||
|
return if (result.isSuccess) {
|
||||||
|
Optional.presented(result.getOrThrow())
|
||||||
|
} else {
|
||||||
|
Optional.absent()
|
||||||
}
|
}
|
||||||
return Optional.absent()
|
|
||||||
}
|
}
|
||||||
|
@@ -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()
|
||||||
|
}
|
@@ -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
|
@@ -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)
|
@@ -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
|
@@ -22,6 +22,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api libs.kt.coroutines.android
|
api libs.kt.coroutines.android
|
||||||
}
|
}
|
||||||
|
dependsOn(jvmMain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import dev.inmo.micro_utils.common.applyDiff
|
import dev.inmo.micro_utils.common.applyDiff
|
||||||
|
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
||||||
|
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
|
||||||
|
*
|
||||||
|
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
|
||||||
|
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||||
|
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||||
|
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||||
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
): SnapshotStateList<T> {
|
): SnapshotStateList<T> {
|
||||||
val state = mutableStateListOf<T>()
|
val state = mutableStateListOf<T>()
|
||||||
subscribeSafelyWithoutExceptions(scope) {
|
val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
|
||||||
|
{
|
||||||
|
withContext(useContextOnChange) {
|
||||||
|
state.applyDiff(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: {
|
||||||
state.applyDiff(it)
|
state.applyDiff(it)
|
||||||
}
|
}
|
||||||
|
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In fact, it is just classcast of [asMutableComposeListState] to [List]
|
||||||
|
*
|
||||||
|
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
|
||||||
|
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||||
|
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||||
|
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||||
|
*
|
||||||
|
* @return Changing in time [List] which follow [Flow] values
|
||||||
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified T> Flow<List<T>>.asComposeList(
|
inline fun <reified T> Flow<List<T>>.asComposeList(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
): List<T> = asMutableComposeListState(scope)
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
|
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)
|
||||||
|
|
||||||
|
@@ -1,35 +1,94 @@
|
|||||||
package dev.inmo.micro_utils.coroutines.compose
|
package dev.inmo.micro_utils.coroutines.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import dev.inmo.micro_utils.common.compose.asState
|
||||||
|
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
||||||
|
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
|
||||||
|
import dev.inmo.micro_utils.coroutines.doInUI
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
|
||||||
|
*
|
||||||
|
* @param initial First value which will be passed to the result [MutableState]
|
||||||
|
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
|
||||||
|
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||||
|
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||||
|
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||||
|
*/
|
||||||
fun <T> Flow<T>.asMutableComposeState(
|
fun <T> Flow<T>.asMutableComposeState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
): MutableState<T> {
|
): MutableState<T> {
|
||||||
val state = mutableStateOf(initial)
|
val state = mutableStateOf(initial)
|
||||||
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
|
||||||
|
{
|
||||||
|
withContext(useContextOnChange) {
|
||||||
|
state.value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: {
|
||||||
|
state.value = it
|
||||||
|
}
|
||||||
|
subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
|
||||||
|
* This conversation will pass its [StateFlow.value] as the first value
|
||||||
|
*
|
||||||
|
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
|
||||||
|
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||||
|
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||||
|
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||||
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.asMutableComposeState(
|
inline fun <T> StateFlow<T>.asMutableComposeState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
): MutableState<T> = asMutableComposeState(value, scope)
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
|
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
|
||||||
|
*
|
||||||
|
* @param initial First value which will be passed to the result [State]
|
||||||
|
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
|
||||||
|
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||||
|
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||||
|
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||||
|
*/
|
||||||
fun <T> Flow<T>.asComposeState(
|
fun <T> Flow<T>.asComposeState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
): State<T> {
|
): State<T> {
|
||||||
val state = asMutableComposeState(initial, scope)
|
val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
|
||||||
return derivedStateOf { state.value }
|
return state.asState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
|
||||||
|
*
|
||||||
|
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
|
||||||
|
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||||
|
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||||
|
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||||
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.asComposeState(
|
inline fun <T> StateFlow<T>.asComposeState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
): State<T> = asComposeState(value, scope)
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
|
): State<T> = asComposeState(value, scope, useContextOnChange, onException)
|
||||||
|
|
||||||
|
@@ -7,17 +7,15 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||||
fun <T> Flow<T>.toMutableState(
|
fun <T> Flow<T>.toMutableState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope
|
||||||
): MutableState<T> {
|
): MutableState<T> = asMutableComposeState(initial, scope)
|
||||||
val state = mutableStateOf(initial)
|
|
||||||
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.toMutableState(
|
inline fun <T> StateFlow<T>.toMutableState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope
|
||||||
): MutableState<T> = toMutableState(value, scope)
|
): MutableState<T> = asMutableComposeState(scope)
|
||||||
|
|
||||||
|
@@ -1,19 +1,15 @@
|
|||||||
package dev.inmo.micro_utils.coroutines
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
|
|
||||||
fun <T> CoroutineScope.actor(
|
fun <T> CoroutineScope.actor(
|
||||||
channelCapacity: Int = Channel.UNLIMITED,
|
channelCapacity: Int = Channel.UNLIMITED,
|
||||||
block: suspend (T) -> Unit
|
block: suspend (T) -> Unit
|
||||||
): Channel<T> {
|
): Channel<T> {
|
||||||
val channel = Channel<T>(channelCapacity)
|
val channel = Channel<T>(channelCapacity)
|
||||||
launch {
|
channel.consumeAsFlow().subscribe(this, block)
|
||||||
for (data in channel) {
|
|
||||||
block(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -6,23 +6,19 @@ import kotlin.coroutines.*
|
|||||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
cancelOnResult: Boolean = true
|
cancelOnResult: Boolean = true
|
||||||
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
|
): Pair<Deferred<T>, T> {
|
||||||
scope.launch(SupervisorJob()) {
|
val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
|
||||||
val scope = this
|
val scope = scope.LinkedSupervisorScope()
|
||||||
forEach {
|
forEach {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
continuation.resume(it to it.await())
|
resultDeferred.complete(it to it.await())
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
return resultDeferred.await().also {
|
||||||
if (cancelOnResult) {
|
if (cancelOnResult) {
|
||||||
forEach {
|
forEach {
|
||||||
try {
|
runCatchingSafely { it.cancel() }
|
||||||
it.cancel()
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 }
|
@@ -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()
|
@@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely(
|
|||||||
safely(onException, block)
|
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(
|
suspend inline fun <T> safelyWithResult(
|
||||||
noinline block: suspend CoroutineScope.() -> T
|
noinline block: suspend CoroutineScope.() -> T
|
||||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
): 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
|
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
||||||
* returning null at one time
|
* returning null at one time
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
|
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||||
|
return scope.launchSynchronously(block)
|
||||||
|
}
|
@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
|
|||||||
var result: Result<T>? = null
|
var result: Result<T>? = null
|
||||||
val objectToSynchronize = Object()
|
val objectToSynchronize = Object()
|
||||||
synchronized(objectToSynchronize) {
|
synchronized(objectToSynchronize) {
|
||||||
launch {
|
launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
result = safelyWithResult(block)
|
result = safelyWithResult(block)
|
||||||
}.invokeOnCompletion {
|
}.invokeOnCompletion {
|
||||||
synchronized(objectToSynchronize) {
|
synchronized(objectToSynchronize) {
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class LaunchInCurrentThreadTests {
|
||||||
|
@Test
|
||||||
|
fun simpleTestThatLaunchInCurrentThreadWorks() {
|
||||||
|
val expectedResult = 10
|
||||||
|
val result = launchInCurrentThread {
|
||||||
|
expectedResult
|
||||||
|
}
|
||||||
|
assertEquals(expectedResult, result)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
|
||||||
|
val testData = 0 until 100
|
||||||
|
|
||||||
|
testData.forEach {
|
||||||
|
val result = launchInCurrentThread {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
assertEquals(it, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
|
||||||
|
val testData = 0 until 100
|
||||||
|
|
||||||
|
suspend fun test(data: Any): Any {
|
||||||
|
return withContext(Dispatchers.Default) {
|
||||||
|
delay(1)
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testData.forEach {
|
||||||
|
val result = launchInCurrentThread {
|
||||||
|
test(it)
|
||||||
|
}
|
||||||
|
assertEquals(it, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
Normal file
50
coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
17
coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
Normal file
17
coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@@ -121,11 +121,11 @@ tasks.dokkaHtml {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
named("jvmMain") {
|
named("jvmMain") {
|
||||||
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
sourceRoots.setFrom(findSourcesWithName("jvmMain"))
|
||||||
}
|
}
|
||||||
|
|
||||||
named("androidMain") {
|
named("androidMain") {
|
||||||
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
|
sourceRoots.setFrom(findSourcesWithName("androidMain"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ allprojects {
|
|||||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||||
|
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
|
||||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||||
|
|
||||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.12.7
|
version=0.16.8
|
||||||
android_code_version=146
|
android_code_version=176
|
||||||
|
@@ -1,36 +1,42 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "1.7.10"
|
kt = "1.7.20"
|
||||||
kt-serialization = "1.4.0"
|
kt-serialization = "1.4.1"
|
||||||
kt-coroutines = "1.6.4"
|
kt-coroutines = "1.6.4"
|
||||||
|
|
||||||
jb-compose = "1.2.0-alpha01-dev764"
|
kslog = "0.5.4"
|
||||||
jb-exposed = "0.39.2"
|
|
||||||
jb-dokka = "1.7.10"
|
|
||||||
|
|
||||||
klock = "3.0.0"
|
jb-compose = "1.2.2"
|
||||||
uuid = "0.5.0"
|
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.3"
|
||||||
|
|
||||||
gh-release = "2.4.1"
|
gh-release = "2.4.1"
|
||||||
|
|
||||||
android-gradle = "7.2.2"
|
koin = "3.2.2"
|
||||||
|
|
||||||
|
android-gradle = "7.3.0"
|
||||||
dexcount = "3.1.0"
|
dexcount = "3.1.0"
|
||||||
|
|
||||||
android-coreKtx = "1.8.0"
|
android-coreKtx = "1.9.0"
|
||||||
android-recyclerView = "1.2.1"
|
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-espresso = "3.4.0"
|
||||||
android-test = "1.1.3"
|
android-test = "1.1.3"
|
||||||
|
|
||||||
android-props-minSdk = "21"
|
android-props-minSdk = "21"
|
||||||
android-props-compileSdk = "32"
|
android-props-compileSdk = "33"
|
||||||
android-props-buildTools = "32.0.0"
|
android-props-buildTools = "33.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
|
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 = { 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" }
|
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-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" }
|
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" }
|
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
||||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
|
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" }
|
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-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
|
||||||
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
|
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
|
||||||
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
|
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-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
|
||||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
|
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
|
||||||
|
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
28
koin/build.gradle
Normal file
28
koin/build.gradle
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
Normal file
12
koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
Normal 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)
|
12
koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
Normal file
12
koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
Normal 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)
|
||||||
|
|
||||||
|
|
8
koin/src/commonMain/kotlin/GetAllDistinct.kt
Normal file
8
koin/src/commonMain/kotlin/GetAllDistinct.kt
Normal 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()
|
||||||
|
|
8
koin/src/commonMain/kotlin/GetAny.kt
Normal file
8
koin/src/commonMain/kotlin/GetAny.kt
Normal 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()
|
||||||
|
|
6
koin/src/commonMain/kotlin/RandomQualifier.kt
Normal file
6
koin/src/commonMain/kotlin/RandomQualifier.kt
Normal 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())
|
13
koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
Normal file
13
koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
Normal 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)
|
12
koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
Normal file
12
koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
Normal 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)
|
||||||
|
|
27
koin/src/jvmMain/kotlin/FactoryWithBinds.kt
Normal file
27
koin/src/jvmMain/kotlin/FactoryWithBinds.kt
Normal 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())
|
||||||
|
}
|
||||||
|
|
@@ -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)
|
||||||
|
}
|
30
koin/src/jvmMain/kotlin/SignleWithBinds.kt
Normal file
30
koin/src/jvmMain/kotlin/SignleWithBinds.kt
Normal 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())
|
||||||
|
}
|
||||||
|
|
14
koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
Normal file
14
koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
Normal 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)
|
||||||
|
}
|
1
koin/src/main/AndroidManifest.xml
Normal file
1
koin/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.koin"/>
|
@@ -15,5 +15,9 @@ kotlin {
|
|||||||
api libs.ktor.client
|
api libs.ktor.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidMain {
|
||||||
|
dependsOn jvmMain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>()
|
@@ -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)
|
|
||||||
}
|
|
@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
|
|||||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||||
*/
|
*/
|
||||||
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
|
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
|
||||||
inline fun <reified T : Any> openBaseWebSocketFlow(
|
inline fun <T : Any> openBaseWebSocketFlow(
|
||||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
||||||
): Flow<T> {
|
): Flow<T> {
|
||||||
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
|||||||
): Flow<T> {
|
): Flow<T> {
|
||||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||||
|
|
||||||
return openBaseWebSocketFlow<T>(checkReconnection) {
|
return openBaseWebSocketFlow(checkReconnection) {
|
||||||
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
send(receiveDeserialized<T>())
|
send(receiveDeserialized<T>())
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit
|
@@ -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
|
|
||||||
)
|
|
@@ -7,13 +7,5 @@ import io.ktor.client.HttpClient
|
|||||||
expect suspend fun HttpClient.tempUpload(
|
expect suspend fun HttpClient.tempUpload(
|
||||||
fullTempUploadDraftPath: String,
|
fullTempUploadDraftPath: String,
|
||||||
file: MPPFile,
|
file: MPPFile,
|
||||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
onUpload: OnUploadCallback = { _, _ -> }
|
||||||
): TemporalFileId
|
): TemporalFileId
|
||||||
|
|
||||||
suspend fun UnifiedRequester.tempUpload(
|
|
||||||
fullTempUploadDraftPath: String,
|
|
||||||
file: MPPFile,
|
|
||||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
|
||||||
): TemporalFileId = client.tempUpload(
|
|
||||||
fullTempUploadDraftPath, file, onUpload
|
|
||||||
)
|
|
||||||
|
@@ -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
|
||||||
|
)
|
@@ -1,6 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.ktor.client
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.MPPFile
|
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 dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE
|
|||||||
suspend fun tempUpload(
|
suspend fun tempUpload(
|
||||||
fullTempUploadDraftPath: String,
|
fullTempUploadDraftPath: String,
|
||||||
file: MPPFile,
|
file: MPPFile,
|
||||||
onUpload: (Long, Long) -> Unit
|
onUpload: OnUploadCallback
|
||||||
): TemporalFileId {
|
): TemporalFileId {
|
||||||
val formData = FormData()
|
val formData = FormData()
|
||||||
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
|
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
|
||||||
|
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
|
||||||
|
|
||||||
formData.append(
|
formData.append(
|
||||||
"data",
|
"data",
|
||||||
@@ -25,7 +28,7 @@ suspend fun tempUpload(
|
|||||||
val request = XMLHttpRequest()
|
val request = XMLHttpRequest()
|
||||||
request.responseType = XMLHttpRequestResponseType.TEXT
|
request.responseType = XMLHttpRequestResponseType.TEXT
|
||||||
request.upload.onprogress = {
|
request.upload.onprogress = {
|
||||||
onUpload(it.loaded.toLong(), it.total.toLong())
|
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
|
||||||
}
|
}
|
||||||
request.onload = {
|
request.onload = {
|
||||||
if (request.status == 200.toShort()) {
|
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(
|
actual suspend fun HttpClient.tempUpload(
|
||||||
fullTempUploadDraftPath: String,
|
fullTempUploadDraftPath: String,
|
||||||
file: MPPFile,
|
file: MPPFile,
|
||||||
onUpload: (uploaded: Long, count: Long) -> Unit
|
onUpload: OnUploadCallback
|
||||||
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)
|
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile
|
|||||||
import io.ktor.client.request.forms.InputProvider
|
import io.ktor.client.request.forms.InputProvider
|
||||||
import io.ktor.utils.io.streams.asInput
|
import io.ktor.utils.io.streams.asInput
|
||||||
|
|
||||||
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
|
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
|
||||||
inputStream().asInput()
|
inputStream().asInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()
|
||||||
|
@@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String
|
|||||||
actual suspend fun HttpClient.tempUpload(
|
actual suspend fun HttpClient.tempUpload(
|
||||||
fullTempUploadDraftPath: String,
|
fullTempUploadDraftPath: String,
|
||||||
file: MPPFile,
|
file: MPPFile,
|
||||||
onUpload: (Long, Long) -> Unit
|
onUpload: OnUploadCallback
|
||||||
): TemporalFileId {
|
): TemporalFileId {
|
||||||
val inputProvider = file.inputProvider()
|
val inputProvider = file.inputProvider()
|
||||||
val fileId = submitFormWithBinaryData(
|
val fileId = submitFormWithBinaryData(
|
||||||
|
@@ -0,0 +1,106 @@
|
|||||||
|
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.InternalSerializationApi
|
||||||
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
import kotlinx.serialization.StringFormat
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@OptIn(InternalSerializationApi::class)
|
||||||
|
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::class.serializer() as SerializationStrategy<in Any>, 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
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.common
|
||||||
|
|
||||||
|
import io.ktor.utils.io.core.Input
|
||||||
|
|
||||||
|
typealias LambdaInputProvider = () -> Input
|
@@ -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()
|
||||||
|
}
|
@@ -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().asOutput().use {
|
||||||
|
input.copyTo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
@@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime
|
|||||||
|
|
||||||
class TemporalFilesRoutingConfigurator(
|
class TemporalFilesRoutingConfigurator(
|
||||||
private val subpath: String = DefaultTemporalFilesSubPath,
|
private val subpath: String = DefaultTemporalFilesSubPath,
|
||||||
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
|
|
||||||
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
|
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
|
||||||
) : ApplicationRoutingConfigurator.Element {
|
) : ApplicationRoutingConfigurator.Element {
|
||||||
interface TemporalFilesUtilizer {
|
interface TemporalFilesUtilizer {
|
||||||
@@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator(
|
|||||||
|
|
||||||
override fun Route.invoke() {
|
override fun Route.invoke() {
|
||||||
post(subpath) {
|
post(subpath) {
|
||||||
unifiedRouter.apply {
|
val multipart = call.receiveMultipart()
|
||||||
val multipart = call.receiveMultipart()
|
|
||||||
|
|
||||||
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
||||||
var part = multipart.readPart()
|
var part = multipart.readPart()
|
||||||
|
|
||||||
while (part != null) {
|
while (part != null) {
|
||||||
if (part is PartData.FileItem) {
|
if (part is PartData.FileItem) {
|
||||||
break
|
break
|
||||||
}
|
|
||||||
part = multipart.readPart()
|
|
||||||
}
|
}
|
||||||
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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()
|
||||||
|
}
|
@@ -78,12 +78,14 @@ private fun printLanguageCodeAndTags(
|
|||||||
indents: String = " "
|
indents: String = " "
|
||||||
): String = if (tag.subtags.isEmpty()) {
|
): String = if (tag.subtags.isEmpty()) {
|
||||||
"""${indents}${baseClassSerializerAnnotationName}
|
"""${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 {
|
} else {
|
||||||
"""
|
"""
|
||||||
${indents}${baseClassSerializerAnnotationName}
|
${indents}${baseClassSerializerAnnotationName}
|
||||||
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
|
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
|
||||||
${indents} override val code: String = "${tag.tag}"
|
${indents} override val code: String = "${tag.tag}"
|
||||||
|
${indents} override val withoutDialect: String
|
||||||
|
${indents} get() = code
|
||||||
|
|
||||||
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
||||||
|
|
||||||
@@ -104,11 +106,14 @@ import kotlinx.serialization.Serializable
|
|||||||
${baseClassSerializerAnnotationName}
|
${baseClassSerializerAnnotationName}
|
||||||
sealed class $baseClassName {
|
sealed class $baseClassName {
|
||||||
abstract val code: String
|
abstract val code: String
|
||||||
|
abstract val withoutDialect: String
|
||||||
|
|
||||||
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
||||||
|
|
||||||
$baseClassSerializerAnnotationName
|
$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
|
override fun toString() = code
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -17,10 +17,11 @@ def fix_name(category, raw_name):
|
|||||||
result += out1
|
result += out1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# https://www.freeformatter.com/mime-types-list.html
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
df = pd.read_html(open('table.html', 'r'))
|
df = pd.read_html(open('table.html', 'r'))
|
||||||
mimes = []
|
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 = row[1][1]
|
||||||
mime_category = mime.split('/', 1)[0]
|
mime_category = mime.split('/', 1)[0]
|
||||||
mime_name = mime.split('/', 1)[1]
|
mime_name = mime.split('/', 1)[1]
|
||||||
@@ -37,7 +38,7 @@ if __name__ == '__main__':
|
|||||||
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
||||||
code2 += ' KnownMimeTypes.Any,\n'
|
code2 += ' KnownMimeTypes.Any,\n'
|
||||||
for key, group in grouped:
|
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)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
|
||||||
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
|
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
|
||||||
for mime in group:
|
for mime in group:
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.micro_utils.mime_types
|
||||||
|
|
||||||
|
data class CustomMimeType(override val raw: String) : MimeType
|
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||||
|
)
|
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.mime_types
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable(MimeTypeSerializer::class)
|
||||||
|
interface MimeType {
|
||||||
|
val raw: String
|
||||||
|
}
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,5 @@
|
|||||||
package dev.inmo.micro_utils.mime_types
|
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 {
|
private val mimesCache = mutableMapOf<String, MimeType>().also {
|
||||||
knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType }
|
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)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
49
mppJsAndJavaProject.gradle
Normal file
49
mppJsAndJavaProject.gradle
Normal 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
|
||||||
|
}
|
@@ -23,7 +23,7 @@ kotlin {
|
|||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib')
|
implementation kotlin('stdlib')
|
||||||
implementation libs.kt.serialization
|
api libs.kt.serialization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commonTest {
|
commonTest {
|
||||||
|
@@ -23,7 +23,7 @@ kotlin {
|
|||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib')
|
implementation kotlin('stdlib')
|
||||||
implementation libs.kt.serialization
|
api libs.kt.serialization
|
||||||
implementation compose.runtime
|
implementation compose.runtime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,18 +33,6 @@ data class PaginationResult<T>(
|
|||||||
results,
|
results,
|
||||||
(pagesNumber * size).toLong()
|
(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() = PaginationResult<T>(0, 0, emptyList(), 0L)
|
||||||
|
@@ -19,29 +19,29 @@ publishing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
developers {
|
developers {
|
||||||
|
|
||||||
developer {
|
developer {
|
||||||
id = "InsanusMokrassar"
|
id = "InsanusMokrassar"
|
||||||
name = "Aleksei Ovsiannikov"
|
name = "Aleksei Ovsiannikov"
|
||||||
email = "ovsyannikov.alexey95@gmail.com"
|
email = "ovsyannikov.alexey95@gmail.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
developer {
|
developer {
|
||||||
id = "000Sanya"
|
id = "000Sanya"
|
||||||
name = "Syrov Aleksandr"
|
name = "Syrov Aleksandr"
|
||||||
email = "000sanya.000sanya@gmail.com"
|
email = "000sanya.000sanya@gmail.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenses {
|
licenses {
|
||||||
|
|
||||||
license {
|
license {
|
||||||
name = "Apache Software License 2.0"
|
name = "Apache Software License 2.0"
|
||||||
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
@@ -49,35 +49,54 @@ publishing {
|
|||||||
maven {
|
maven {
|
||||||
name = "GithubPackages"
|
name = "GithubPackages"
|
||||||
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
||||||
|
|
||||||
credentials {
|
credentials {
|
||||||
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
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')
|
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)) {
|
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||||
maven {
|
maven {
|
||||||
name = "sonatype"
|
name = "sonatype"
|
||||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||||
|
|
||||||
credentials {
|
credentials {
|
||||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||||
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.hasProperty("signing.gnupg.keyName")) {
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
|
||||||
signing {
|
signing {
|
||||||
useGpgCmd()
|
useGpgCmd()
|
||||||
|
|
||||||
sign publishing.publications
|
sign publishing.publications
|
||||||
}
|
}
|
||||||
|
|
||||||
task signAll {
|
task signAll {
|
||||||
tasks.withType(Sign).forEach {
|
tasks.withType(Sign).forEach {
|
||||||
dependsOn(it)
|
dependsOn(it)
|
||||||
|
@@ -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"}}}
|
@@ -10,12 +10,14 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo {
|
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo {
|
||||||
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
||||||
kvCache.set(id, it)
|
kvCache.set(id, it)
|
||||||
})
|
})
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||||
@@ -28,7 +30,7 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||||
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CacheRepo {
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo {
|
||||||
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
|
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
||||||
@@ -72,6 +74,8 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
interface CacheRepo
|
interface CacheRepo {
|
||||||
|
suspend fun invalidate()
|
||||||
|
}
|
||||||
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CommonCacheRepo.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CommonCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any inheritor of this should work with next logic: try to take data from some [dev.inmo.micro_utils.repos.cache.cache.KVCache] and,
|
||||||
|
* if not exists, take from origin and save to the cache for future reuse
|
||||||
|
*/
|
||||||
|
interface CommonCacheRepo : CacheRepo
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/FallbackCacheRepo.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/FallbackCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any inheritor of this should work with next logic: try to take data from their original repo, if successful - save data to internal
|
||||||
|
* [dev.inmo.micro_utils.repos.cache.cache.FullKVCache] or try to take data from that internal cache
|
||||||
|
*/
|
||||||
|
interface FallbackCacheRepo : CacheRepo
|
@@ -1,7 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -11,9 +10,21 @@ import kotlinx.coroutines.flow.*
|
|||||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: KVCache<Key, Value>,
|
protected open val kvCache: KVCache<Key, Value>,
|
||||||
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo {
|
) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||||
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
|
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
|
||||||
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
|
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
|
||||||
|
|
||||||
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
|
return keys(pagination, reversed).let {
|
||||||
|
it.changeResultsUnchecked(
|
||||||
|
it.results.mapNotNull {
|
||||||
|
get(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
@@ -24,9 +35,11 @@ open class KeyValueCacheRepo<Key,Value>(
|
|||||||
parentRepo: KeyValueRepo<Key, Value>,
|
parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, Value>,
|
kvCache: KVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo {
|
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo {
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.utils.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -13,21 +11,27 @@ import kotlinx.coroutines.flow.*
|
|||||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: KVCache<Key, List<Value>>
|
protected open val kvCache: KVCache<Key, List<Value>>
|
||||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
|
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
return kvCache.get(k) ?.paginate(
|
return getAll(k, reversed).paginate(
|
||||||
pagination.let { if (reversed) it.reverse(count(k)) else it }
|
pagination
|
||||||
) ?.let {
|
)
|
||||||
if (reversed) it.copy(results = it.results.reversed()) else it
|
|
||||||
} ?: parentRepo.get(k, pagination, reversed)
|
|
||||||
}
|
}
|
||||||
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
|
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
|
||||||
return kvCache.get(k) ?.let {
|
return kvCache.get(k) ?.let {
|
||||||
if (reversed) it.reversed() else it
|
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)
|
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
@@ -38,10 +42,24 @@ open class KeyValuesCacheRepo<Key,Value>(
|
|||||||
parentRepo: KeyValuesRepo<Key, Value>,
|
parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, List<Value>>,
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
|
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
|
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
kvCache.set(
|
||||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
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)
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
||||||
|
@@ -25,6 +25,9 @@ open class SimpleKVCache<K, V>(
|
|||||||
kvParent.unset(it)
|
kvParent.unset(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
do {
|
||||||
|
val removed = cacheQueue.remove(k)
|
||||||
|
} while (removed)
|
||||||
cacheQueue.addLast(k)
|
cacheQueue.addLast(k)
|
||||||
kvParent.set(k, v)
|
kvParent.set(k, v)
|
||||||
}
|
}
|
||||||
|
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt
vendored
Normal file
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realizations should [wrap] the work with some conditions like retries on exceptions, calling timeout, etc.
|
||||||
|
*
|
||||||
|
* @see Timeouted
|
||||||
|
* @see Direct
|
||||||
|
*/
|
||||||
|
interface ActionWrapper {
|
||||||
|
/**
|
||||||
|
* Should execute [block] to take the result [T], but may return failure in case when something went wrong.
|
||||||
|
* This method should never throw any [Exception]
|
||||||
|
*/
|
||||||
|
suspend fun <T> wrap(block: suspend () -> T): Result<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type of [ActionWrapper]s will use [withTimeout]([timeoutMillis]) and if original call
|
||||||
|
* will not return anything in that timeout just return [Result] with failure
|
||||||
|
*/
|
||||||
|
class Timeouted(private val timeoutMillis: Long) : ActionWrapper {
|
||||||
|
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely {
|
||||||
|
withTimeout(timeoutMillis) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* It is passthrough variant of [ActionWrapper] which will just call incoming block with wrapping into [runCatchingSafely]
|
||||||
|
*/
|
||||||
|
object Direct : ActionWrapper {
|
||||||
|
|
||||||
|
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely { block() }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteCRUDRepo<RegisteredObject, Id, InputObject> by AutoRecacheWriteCRUDRepo(originalRepo, scope, kvCache, idGetter),
|
||||||
|
CRUDRepo<RegisteredObject, Id, InputObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
|
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : ReadCRUDRepo<RegisteredObject, Id>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(id: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(id)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.getByPagination(pagination)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
} ?: kvCache.values(pagination)
|
||||||
|
|
||||||
|
override suspend fun getIdsByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.getIdsByPagination(pagination)
|
||||||
|
}.getOrElse { kvCache.keys(pagination) }
|
||||||
|
|
||||||
|
override suspend fun getById(id: Id): RegisteredObject? = actionWrapper.wrap {
|
||||||
|
originalRepo.getById(id)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
} ?: kvCache.get(id)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
|
open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
|
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
|
||||||
|
override val deletedObjectsIdsFlow: Flow<Id>
|
||||||
|
get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
override val newObjectsFlow: Flow<RegisteredObject>
|
||||||
|
get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged()
|
||||||
|
override val updatedObjectsFlow: Flow<RegisteredObject>
|
||||||
|
get() = originalRepo.updatedObjectsFlow
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = merge(
|
||||||
|
originalRepo.newObjectsFlow,
|
||||||
|
originalRepo.updatedObjectsFlow,
|
||||||
|
).subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
values: List<UpdatedValuePair<Id, InputObject>>
|
||||||
|
): List<RegisteredObject> = originalRepo.update(values).onEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
id: Id,
|
||||||
|
value: InputObject
|
||||||
|
): RegisteredObject? = originalRepo.update(id, value) ?.also {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<Id>) = originalRepo.deleteById(ids).also {
|
||||||
|
kvCache.unset(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(values: List<InputObject>): List<RegisteredObject> = originalRepo.create(values).onEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : AutoRecacheReadKeyValueRepo<Id, RegisteredObject> (
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteKeyValueRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValueRepo(originalRepo, scope, kvCache),
|
||||||
|
KeyValueRepo<Id, RegisteredObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unsetWithValues(toUnset)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,96 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : ReadKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(key)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(k: Id): RegisteredObject? = actionWrapper.wrap {
|
||||||
|
originalRepo.get(k)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(k, it)
|
||||||
|
} ?: kvCache.get(k)
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.values(pagination, reversed)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
} ?: kvCache.values(pagination, reversed)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: RegisteredObject,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(v, pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(v, pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user