82 lines
2.9 KiB
Kotlin
82 lines
2.9 KiB
Kotlin
package com.android.trisolarisserver.component
|
|
|
|
import com.android.trisolarisserver.controller.GuestDocumentResponse
|
|
import com.android.trisolarisserver.controller.toResponse
|
|
import com.android.trisolarisserver.db.repo.GuestDocumentRepo
|
|
import com.fasterxml.jackson.databind.ObjectMapper
|
|
import org.springframework.scheduling.annotation.Scheduled
|
|
import org.springframework.stereotype.Component
|
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
|
import java.io.IOException
|
|
import java.util.UUID
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
|
|
@Component
|
|
class GuestDocumentEvents(
|
|
private val guestDocumentRepo: GuestDocumentRepo,
|
|
private val objectMapper: ObjectMapper
|
|
) {
|
|
private val emitters: MutableMap<GuestDocKey, CopyOnWriteArrayList<SseEmitter>> = ConcurrentHashMap()
|
|
|
|
fun subscribe(propertyId: UUID, guestId: UUID): SseEmitter {
|
|
val key = GuestDocKey(propertyId, guestId)
|
|
val emitter = SseEmitter(0L)
|
|
emitters.computeIfAbsent(key) { CopyOnWriteArrayList() }.add(emitter)
|
|
emitter.onCompletion { emitters[key]?.remove(emitter) }
|
|
emitter.onTimeout { emitters[key]?.remove(emitter) }
|
|
emitter.onError { emitters[key]?.remove(emitter) }
|
|
try {
|
|
emitter.send(SseEmitter.event().name("guest-documents").data(buildSnapshot(propertyId, guestId)))
|
|
} catch (_: IOException) {
|
|
emitters[key]?.remove(emitter)
|
|
}
|
|
return emitter
|
|
}
|
|
|
|
fun emit(propertyId: UUID, guestId: UUID) {
|
|
val key = GuestDocKey(propertyId, guestId)
|
|
val list = emitters[key] ?: return
|
|
val data = buildSnapshot(propertyId, guestId)
|
|
val dead = mutableListOf<SseEmitter>()
|
|
for (emitter in list) {
|
|
try {
|
|
emitter.send(SseEmitter.event().name("guest-documents").data(data))
|
|
} catch (_: IOException) {
|
|
dead.add(emitter)
|
|
}
|
|
}
|
|
if (dead.isNotEmpty()) {
|
|
list.removeAll(dead.toSet())
|
|
}
|
|
}
|
|
|
|
@Scheduled(fixedDelayString = "25000")
|
|
fun heartbeat() {
|
|
emitters.forEach { (_, list) ->
|
|
val dead = mutableListOf<SseEmitter>()
|
|
for (emitter in list) {
|
|
try {
|
|
emitter.send(SseEmitter.event().name("ping").data("ok"))
|
|
} catch (_: IOException) {
|
|
dead.add(emitter)
|
|
}
|
|
}
|
|
if (dead.isNotEmpty()) {
|
|
list.removeAll(dead.toSet())
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun buildSnapshot(propertyId: UUID, guestId: UUID): List<GuestDocumentResponse> {
|
|
return guestDocumentRepo
|
|
.findByPropertyIdAndGuestIdOrderByUploadedAtDesc(propertyId, guestId)
|
|
.map { it.toResponse(objectMapper) }
|
|
}
|
|
}
|
|
|
|
private data class GuestDocKey(
|
|
val propertyId: UUID,
|
|
val guestId: UUID
|
|
)
|