Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.22.5 #492

Merged
merged 5 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.22.5

* `Versions`:
* `Compose`: `1.7.0-beta02` -> `1.7.0-rc01`
* `SQLite`: `3.46.1.2` -> `3.46.1.3`
* `AndroidXFragment`: `1.8.3` -> `1.8.4`
* `Common`:
* Add extension `withReplacedAt`/`withReplaced` ([#489](https://github.com/InsanusMokrassar/MicroUtils/issues/489))
* `Coroutines`:
* Add extension `Flow.debouncedBy`
* `Ktor`:
* `Server`:
* Add `KtorApplicationConfigurator.Routing.Static` as solution for [#488](https://github.com/InsanusMokrassar/MicroUtils/issues/488)

## 0.22.4

* `Versions`:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dev.inmo.micro_utils.common

fun <T> Iterable<T>.withReplacedAt(i: Int, block: (T) -> T): List<T> = take(i) + block(elementAt(i)) + drop(i + 1)
fun <T> Iterable<T>.withReplaced(t: T, block: (T) -> T): List<T> = withReplacedAt(indexOf(t), block)

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.common

import kotlin.test.Test
import kotlin.test.assertEquals

class WithReplacedTest {
@Test
fun testReplaced() {
val data = 0 until 10
val testData = Int.MAX_VALUE

for (i in 0 until data.last) {
val withReplaced = data.withReplacedAt(i) {
testData
}
val dataAsMutableList = data.toMutableList()
dataAsMutableList[i] = testData
assertEquals(withReplaced, dataAsMutableList.toList())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dev.inmo.micro_utils.coroutines

import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.jvm.JvmInline
import kotlin.time.Duration

@JvmInline
private value class DebouncedByData<T>(
val millisToData: Pair<Long, T>
)

fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): Flow<T> = channelFlow {
val jobs = mutableMapOf<Any?, Job>()
val mutex = Mutex()
subscribe(this) {
mutex.withLock {
val marker = markerFactory(it)
lateinit var job: Job
job = async {
delay(timeout(it))
mutex.withLock {
if (jobs[marker] === job) {
[email protected](it)
jobs.remove(marker)
}
}
}
jobs[marker] ?.cancel()
jobs[marker] = job
}
}
}

fun <T> Flow<T>.debouncedBy(timeout: Long, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout }, markerFactory)
fun <T> Flow<T>.debouncedBy(timeout: Duration, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout.inWholeMilliseconds }, markerFactory)
42 changes: 42 additions & 0 deletions coroutines/src/commonTest/kotlin/DebouncedByTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import dev.inmo.micro_utils.coroutines.debouncedBy
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class DebouncedByTests {
@Test
fun testThatParallelDebouncingWorksCorrectly() = runTest {
val dataToMarkerFactories = listOf(
1 to 0,
2 to 1,
3 to 2,
4 to 0,
5 to 1,
6 to 2,
7 to 0,
8 to 1,
9 to 2,
)

val collected = mutableListOf<Int>()

dataToMarkerFactories.asFlow().debouncedBy(10L) {
it.second
}.collect {
when (it.second) {
0 -> assertEquals(7, it.first)
1 -> assertEquals(8, it.first)
2 -> assertEquals(9, it.first)
else -> error("wtf")
}
collected.add(it.first)
}

val expectedList = listOf(7, 8, 9)
assertEquals(expectedList, collected)
assertTrue { collected.containsAll(expectedList) }
assertTrue { expectedList.containsAll(collected) }
}
}
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ crypto_js_version=4.1.1
# Project data

group=dev.inmo
version=0.22.4
android_code_version=270
version=0.22.5
android_code_version=271
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ kt-coroutines = "1.9.0"

kslog = "1.3.6"

jb-compose = "1.7.0-beta02"
jb-compose = "1.7.0-rc01"
jb-exposed = "0.55.0"
jb-dokka = "1.9.20"

sqlite = "3.46.1.2"
sqlite = "3.46.1.3"

korlibs = "5.4.0"
uuid = "0.8.4"
Expand All @@ -34,7 +34,7 @@ dexcount = "4.0.0"
android-coreKtx = "1.13.1"
android-recyclerView = "1.3.2"
android-appCompat = "1.7.0"
android-fragment = "1.8.3"
android-fragment = "1.8.4"
android-espresso = "3.6.1"
android-test = "1.2.1"
android-compose-material3 = "1.3.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,101 @@
package dev.inmo.micro_utils.ktor.server.configurators

import io.ktor.server.application.Application
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.plugins.cachingheaders.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.io.File

interface KtorApplicationConfigurator {
@Serializable
class Routing(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun Route.invoke() }
private val rootInstaller = Element {
elements.forEach {
it.apply { invoke() }
}
}

override fun Application.configure() {
pluginOrNull(io.ktor.server.routing.Routing) ?.apply {
rootInstaller.apply { invoke() }
} ?: install(io.ktor.server.routing.Routing) {
rootInstaller.apply { invoke() }
}
}

/**
* @param pathToFolder Contains [Pair]s where firsts are paths in urls and seconds are folders file paths
* @param pathToResource Contains [Pair]s where firsts are paths in urls and seconds are packages in resources
*/
class Static(
private val pathToFolder: List<Pair<String, String>> = emptyList(),
private val pathToResource: List<Pair<String, String>> = emptyList(),
) : Element {
override fun Route.invoke() {
pathToFolder.forEach {
staticFiles(
it.first,
File(it.second)
)
}
pathToResource.forEach {
staticResources(
it.first,
it.second
)
}
}
}
}

class StatusPages(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun StatusPagesConfig.invoke() }

override fun Application.configure() {
install(StatusPages) {
elements.forEach {
it.apply { invoke() }
}
}
}
}

class Sessions(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun SessionsConfig.invoke() }

override fun Application.configure() {
install(Sessions) {
elements.forEach {
it.apply { invoke() }
}
}
}
}

class CachingHeaders(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun CachingHeadersConfig.invoke() }

override fun Application.configure() {
install(CachingHeaders) {
elements.forEach {
it.apply { invoke() }
}
}
}
}

fun Application.configure()
}
Loading