diff --git a/src/commonMain/kotlin/de/kif/common/DateFormat.kt b/src/commonMain/kotlin/de/kif/common/DateFormat.kt index af1c8a2..d336d8b 100644 --- a/src/commonMain/kotlin/de/kif/common/DateFormat.kt +++ b/src/commonMain/kotlin/de/kif/common/DateFormat.kt @@ -2,7 +2,6 @@ package de.kif.common import com.soywiz.klock.* import com.soywiz.klock.locale.german -import kotlin.math.ceil fun formatDateTime(unix: Long, timezone: Long) = DateFormat("EEEE, d. MMMM y HH:mm") @@ -25,8 +24,10 @@ fun formatTime(unix: Long, timezone: Long) = .format(DateTimeTz.utc(DateTime(unix), TimezoneOffset(timezone.toDouble()))) fun formatTimeDiff(time: Long, now: Long, timezone: Long): String { - var dt = ceil(((time - now) / 1000) / 60.0).toLong() + var dt = (time - now) / 1000 + val seconds = dt % 60 + dt /= 60 val minutes = dt % 60 dt /= 60 val hours = dt % 24 @@ -34,8 +35,12 @@ fun formatTimeDiff(time: Long, now: Long, timezone: Long): String { val days = dt return when { - days > 1L -> "in $days Tagen" - days > 0L -> "morgen" + days > 1L -> { + "in $days Tagen" + } + days > 0L -> { + "morgen" + } hours > 1L -> { val nowHour = DateFormat("HH") .withLocale(KlockLocale.german) @@ -49,11 +54,18 @@ fun formatTimeDiff(time: Long, now: Long, timezone: Long): String { "morgen" } else "um $ht" } - hours > 0L -> "in " + hours.toTimeString() + ":" + minutes.toTimeString() - minutes > 1L -> "in 00:" + minutes.toTimeString() - else -> "in > 1 Minute" + hours > 0L -> { + "in " + hours.toString().padStart(2, '0') + ":" + (minutes + if (seconds > 0) 1 else 0).toString().padStart( + 2, + '0' + ) + } + minutes > 0L -> { + "in 00:" + (minutes + if (seconds > 0) 1 else 0).toString().padStart(2, '0') + } + seconds > 0L -> { + "in > 1 Minute" + } + else -> "---" } } - -@Suppress("NOTHING_TO_INLINE") -private inline fun Number.toTimeString() = toString().padStart(2, '0') diff --git a/src/jsMain/kotlin/de/kif/frontend/PushServiceClient.kt b/src/jsMain/kotlin/de/kif/frontend/PushServiceClient.kt index 8e4863f..100321f 100644 --- a/src/jsMain/kotlin/de/kif/frontend/PushServiceClient.kt +++ b/src/jsMain/kotlin/de/kif/frontend/PushServiceClient.kt @@ -3,15 +3,11 @@ package de.kif.frontend import de.kif.common.MessageBox import de.kif.common.MessageType import de.kif.common.RepositoryType -import de.kif.common.Serialization import de.kif.frontend.repository.* import de.westermann.kwebview.clearInterval import de.westermann.kwebview.createHtmlView import de.westermann.kwebview.interval import kotlinx.serialization.DynamicObjectParser -import org.w3c.dom.EventSource -import org.w3c.dom.MessageEvent -import org.w3c.dom.events.EventListener import org.w3c.dom.get import org.w3c.xhr.XMLHttpRequest import kotlin.browser.document @@ -19,8 +15,7 @@ import kotlin.browser.window class PushServiceClient { private val prefix = js("prefix") - private val pollingUrl = "$prefix/api/updates" - private val eventUrl = "$prefix/api/events" + private val url = "$prefix/api/updates" private val body = document.body ?: createHtmlView() private val parser = DynamicObjectParser() @@ -60,17 +55,16 @@ class PushServiceClient { } } - private fun onError(code: Int): Boolean { + private fun onError(code: Int) { if (errorTimeout > 0) { errorTimeout-- - return false + return } if (!body.classList.contains("offline")) { console.log("Offline reason: $code") } body.classList.add("offline") - return true } private fun request() { @@ -88,57 +82,21 @@ class PushServiceClient { } else { onError(-1) } + } else { onError(xmlHttpRequest.status.toInt()) } } - Unit } catch (e: Exception) { console.error(e) onError(-2) } } - xmlHttpRequest.open("GET", "$pollingUrl?timestamp=$timestamp", true) + xmlHttpRequest.open("GET", "$url?timestamp=$timestamp", true) xmlHttpRequest.overrideMimeType("application/json") xmlHttpRequest.send() } - private fun initEventSource() { - val eventSource = EventSource(eventUrl) - var timeout = 3 - - eventSource.addEventListener("update", EventListener { - val event = it as? MessageEvent ?: return@EventListener - val message = event.data as? String ?: return@EventListener - onMessage(Serialization.parse(MessageBox.serializer(), message)) - }) - eventSource.addEventListener("ping", EventListener { - timeout = 3 - val event = it as? MessageEvent ?: return@EventListener - val s = event.data as? String ?: return@EventListener - if (s != signature) { - reload() - } - }) - - intervalId = interval(500) { - timeout -= 1 - if (timeout <= 0) { - if (onError(-1)) { - val id = intervalId - if (id != null) { - clearInterval(id) - intervalId = null - } - - intervalId = interval(500) { - request() - } - } - } - } - } - private val messageHandlers: List = listOf( RoomRepository.handler, ScheduleRepository.handler, @@ -150,19 +108,8 @@ class PushServiceClient { ) init { - try { - initEventSource() - } catch (e: Exception) { - console.log("Cannot connect to event source, use polling fallback!") - val id = intervalId - if (id != null) { - clearInterval(id) - intervalId = null - } - - intervalId = interval(500) { - request() - } + intervalId = interval(500) { + request() } } diff --git a/src/jvmMain/kotlin/de/kif/backend/util/PushService.kt b/src/jvmMain/kotlin/de/kif/backend/util/PushService.kt index 59e3099..d3d4353 100644 --- a/src/jvmMain/kotlin/de/kif/backend/util/PushService.kt +++ b/src/jvmMain/kotlin/de/kif/backend/util/PushService.kt @@ -3,21 +3,18 @@ package de.kif.backend.util import de.kif.backend.repository.* import de.kif.backend.route.api.error import de.kif.backend.route.api.success -import de.kif.common.* +import de.kif.common.Message +import de.kif.common.MessageBox +import de.kif.common.MessageType +import de.kif.common.RepositoryType import de.kif.common.model.Post import io.ktor.application.call -import io.ktor.http.CacheControl -import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.response.cacheControl -import io.ktor.response.respondTextWriter import io.ktor.routing.Route import io.ktor.routing.get -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.broadcast -import kotlinx.coroutines.runBlocking import kotlinx.html.currentTimeMillis import kotlin.concurrent.thread +import kotlin.math.abs import kotlin.math.max object PushService { @@ -27,30 +24,16 @@ object PushService { private val messages: MutableList> = mutableListOf() - private val channel = Channel() - val broadcast = channel.broadcast() - /** * Save the message with the current timestamp */ - suspend fun notify(type: MessageType, repository: RepositoryType, id: Long) { + fun notify(type: MessageType, repository: RepositoryType, id: Long) { val timestamp = System.currentTimeMillis() val message = Message(type, repository, id) synchronized(messages) { messages += Pair(timestamp, message) } - - channel.send( - SSE.Message( - MessageBox( - System.currentTimeMillis(), - signature, - true, - listOf(message) - ) - ) - ) } private fun getIndexOfTimestamp(timestamp: Long): Int { @@ -110,17 +93,6 @@ object PushService { } } } - - fun ping() { - runBlocking { - channel.send(SSE.Ping) - } - } -} - -sealed class SSE { - class Message(val messageBox: MessageBox) : SSE() - object Ping: SSE() } fun Route.pushService() { @@ -132,42 +104,12 @@ fun Route.pushService() { val messageBox = PushService.getMessages(timestamp) - call.response.cacheControl(CacheControl.NoCache(null)) call.success(messageBox) } catch (_: Exception) { call.error(HttpStatusCode.InternalServerError) } } - get("/api/events") { - val events = PushService.broadcast.openSubscription() - try { - call.response.cacheControl(CacheControl.NoCache(null)) - call.respondTextWriter(contentType = ContentType.Text.EventStream) { - write("retry: 1000\n") - write("\n") - - for (event in events) { - write("id: 0\n") - when (event) { - is SSE.Ping -> { - write("event: ping\n") - write("data: ${PushService.signature}\n") - } - is SSE.Message -> { - write("event: update\n") - write("data: ${Serialization.stringify(MessageBox.serializer(), event.messageBox)}\n") - } - } - write("\n") - flush() - } - } - } finally { - events.cancel() - } - } - RoomRepository.registerPushService() ScheduleRepository.registerPushService() TrackRepository.registerPushService() @@ -179,19 +121,11 @@ fun Route.pushService() { thread( start = true, isDaemon = true, - name = "push-service" + name = "PushServiceGC" ) { - var gc = currentTimeMillis() + 1000 * 60 * 10 while (true) { - Thread.sleep(500) - PushService.ping() - - val now = currentTimeMillis() - if (gc < now) { - gc = now + 1000 * 60 * 10 - PushService.gc(now - 1000 * 60) - } + PushService.gc(currentTimeMillis() - 1000 * 60) + Thread.sleep(1000 * 60 * 5) } - } } diff --git a/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt b/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt index ac288df..d1c5959 100644 --- a/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt +++ b/src/jvmMain/kotlin/de/kif/backend/view/MainTemplate.kt @@ -69,7 +69,7 @@ class MainTemplate( script { unsafe { +"let prefix = '$prefix';\n" - +"require.config({waitSeconds: 120, baseUrl: '$prefix/static'});\n" + +"require.config({waitSeconds: 60, baseUrl: '$prefix/static'});\n" +"require([${Resources.jsModules}]);\n" } }