diff --git a/config/vkoshp/2023/advanced.json b/config/vkoshp/2023/advanced.json index 09f355d85..f6af648a8 100644 --- a/config/vkoshp/2023/advanced.json +++ b/config/vkoshp/2023/advanced.json @@ -69,13 +69,13 @@ "screen": { "type": "WebRTCGrabberConnection", "peerName": "{grabberPeerName}", - "url": "{grabberUrl}", + "url": "http://{grabberIp}:8080", "streamType": "desktop", "credential": "live" }, "camera": { "type": "WebRTCGrabberConnection", - "url": "{grabberUrl}", + "url": "http://{grabberIp}:8080", "peerName": "{grabberPeerName}", "streamType": "webcam", "credential": "live" @@ -125,11 +125,11 @@ "nsk(\\d\\d\\d)": "N$1", "kaz(\\d\\d\\d)": "K$1" }, - "grabberUrl": { - "spb(\\d\\d\\d)": "http://192.168.0.112:8000", - "geo(\\d\\d\\d)": "http://192.168.0.175:8000", - "nsk(\\d\\d\\d)": "http://192.168.0.175:8000", - "kaz(\\d\\d\\d)": "http://192.168.0.175:8000" + "grabberIp": { + "spb(\\d\\d\\d)": "192.168.0.112", + "geo(\\d\\d\\d)": "192.168.0.175", + "nsk(\\d\\d\\d)": "192.168.0.175", + "kaz(\\d\\d\\d)": "192.168.0.175" } }, "groupRegex": { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 835c94d31..f4f4ddec7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,7 @@ ktor-client-auth = { version.ref = "ktor", group = "io.ktor", name = "ktor ktor-client-contentNegotiation = { version.ref = "ktor", group = "io.ktor", name = "ktor-client-content-negotiation" } ktor-serialization-kotlinx-json = { version.ref = "ktor", group = "io.ktor", name = "ktor-serialization-kotlinx-json" } +ktor-http = { version.ref = "ktor", group = "io.ktor", name = "ktor-http" } ktor-server-auth = { version.ref = "ktor", group = "io.ktor", name = "ktor-server-auth" } ktor-server-autoHeadResponse = { version.ref = "ktor", group = "io.ktor", name = "ktor-server-auto-head-response" } diff --git a/src/cds/core/build.gradle.kts b/src/cds/core/build.gradle.kts index 4b909e2ca..7e8cf2c5c 100644 --- a/src/cds/core/build.gradle.kts +++ b/src/cds/core/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation(projects.cds.utils) implementation(libs.kotlin.reflect) implementation(libs.kotlinx.serialization.json5) + implementation(libs.ktor.http) ksp(projects.ksp) compileOnly(projects.ksp) } \ No newline at end of file diff --git a/src/cds/core/src/main/kotlin/org/icpclive/cds/adapters/AdvancedPropertiesAdapter.kt b/src/cds/core/src/main/kotlin/org/icpclive/cds/adapters/AdvancedPropertiesAdapter.kt index d60767c08..2bd7852af 100644 --- a/src/cds/core/src/main/kotlin/org/icpclive/cds/adapters/AdvancedPropertiesAdapter.kt +++ b/src/cds/core/src/main/kotlin/org/icpclive/cds/adapters/AdvancedPropertiesAdapter.kt @@ -3,6 +3,7 @@ package org.icpclive.cds.adapters +import io.ktor.http.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* @@ -21,18 +22,27 @@ private data object Trigger : AdvancedAdapterEvent internal object AdvancedPropertiesAdapter -private val templateRegex = kotlin.text.Regex("\\{([a-z0-9A-Z_-]*)}") +private val templateRegex = kotlin.text.Regex("\\{(!?[a-z0-9A-Z_-]*)}") private fun String.applyTemplate(valueProvider: (String) -> String?) = replace(templateRegex) { valueProvider(it.groups[1]!!.value) ?: it.value } +private fun urlEncoded(block: (String) -> String?) : (String) -> String? = l@{ + block(it.removePrefix("!"))?.run { + if (it.startsWith("!")) + this + else + encodeURLParameter() + } +} + private fun MediaType.applyTemplate(valueProvider: (String) -> String?) = when (this) { - is MediaType.Photo -> copy(url = url.applyTemplate(valueProvider)) - is MediaType.Video -> copy(url = url.applyTemplate(valueProvider)) - is MediaType.Object -> copy(url = url.applyTemplate(valueProvider)) - is MediaType.WebRTCProxyConnection -> copy(url = url.applyTemplate(valueProvider)) + is MediaType.Photo -> copy(url = url.applyTemplate(urlEncoded(valueProvider))) + is MediaType.Video -> copy(url = url.applyTemplate(urlEncoded(valueProvider))) + is MediaType.Object -> copy(url = url.applyTemplate(urlEncoded(valueProvider))) + is MediaType.WebRTCProxyConnection -> copy(url = url.applyTemplate(urlEncoded(valueProvider))) is MediaType.WebRTCGrabberConnection -> copy( - url = url.applyTemplate(valueProvider), + url = url.applyTemplate(urlEncoded(valueProvider)), peerName = peerName.applyTemplate(valueProvider), credential = credential?.applyTemplate(valueProvider) ) @@ -139,15 +149,20 @@ private fun mergeMaps(original: Map, override: Map) = buildM private fun TeamOverrideTemplate.instantiateTemplate( teams: List, valueProvider: TeamInfo.(String) -> String?, -) = teams.associate { - it.contestSystemId to TeamInfoOverride( - hashTag = hashTag?.applyTemplate { name -> it.valueProvider(name) }, - fullName = fullName?.applyTemplate { name -> it.valueProvider(name) }, - displayName = displayName?.applyTemplate { name -> it.valueProvider(name) }, - medias = medias?.mapValues { (_, v) -> v?.applyTemplate { name -> it.valueProvider(name) } } - ) +) = teams.associate { team -> + team.contestSystemId to instantiateTemplate { team.valueProvider(it) } } +internal fun TeamOverrideTemplate.instantiateTemplate( + valueProvider: (String) -> String?, +): TeamInfoOverride = TeamInfoOverride( + hashTag = hashTag?.applyTemplate(valueProvider), + fullName = fullName?.applyTemplate(valueProvider), + displayName = displayName?.applyTemplate(valueProvider), + medias = medias?.mapValues { (_, v) -> v?.applyTemplate(valueProvider) } +) + + private fun List.filterNotSubmitted(show: Boolean?, submittedTeams: Set) = if (show != false) { this } else { diff --git a/src/cds/core/src/test/kotlin/TeamInfoOverrideTemplateTest.kt b/src/cds/core/src/test/kotlin/TeamInfoOverrideTemplateTest.kt new file mode 100644 index 000000000..aeb2836fc --- /dev/null +++ b/src/cds/core/src/test/kotlin/TeamInfoOverrideTemplateTest.kt @@ -0,0 +1,38 @@ +import org.icpclive.cds.adapters.instantiateTemplate +import org.icpclive.cds.api.MediaType +import org.icpclive.cds.api.TeamMediaType +import org.icpclive.cds.tunning.TeamOverrideTemplate +import kotlin.test.* + +object TeamInfoOverrideTemplateTest { + private fun TeamOverrideTemplate.instantiate(map: Map) = instantiateTemplate { + map[it] + } + + @Test + fun `check url encodes`() { + val template = TeamOverrideTemplate( + displayName = "{teamName}", + medias = mapOf( + TeamMediaType.CAMERA to MediaType.Photo("http://photos-server/{teamName}"), + TeamMediaType.REACTION_VIDEO to MediaType.WebRTCGrabberConnection( + url = "{!grabberUrl}", + peerName = "{teamName}", + streamType = "", + credential = null + ) + ) + ) + val teamName = "Team name with spaces & other / strange : symbols?" + val teamReplaced = "Team%20name%20with%20spaces%20%26%20other%20%2F%20strange%20%3A%20symbols%3F" + val url = "http://this-should-not-be-replaced:12345/url" + val instantiated = template.instantiate(mapOf( + "teamName" to teamName, + "grabberUrl" to url + )) + assertEquals(teamName, instantiated.displayName) + assertEquals("http://photos-server/${teamReplaced}", (instantiated.medias?.get(TeamMediaType.CAMERA) as MediaType.Photo).url) + assertEquals(url, (instantiated.medias?.get(TeamMediaType.REACTION_VIDEO) as MediaType.WebRTCGrabberConnection).url) + assertEquals(teamName, (instantiated.medias?.get(TeamMediaType.REACTION_VIDEO) as MediaType.WebRTCGrabberConnection).peerName) + } +} \ No newline at end of file