You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

89 lines
3.3 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. package net.aiterp.git.ykonsole2.application.routes.ws
  2. import io.ktor.server.routing.*
  3. import io.ktor.server.websocket.*
  4. import io.ktor.websocket.*
  5. import kotlinx.coroutines.Job
  6. import kotlinx.coroutines.cancelAndJoin
  7. import kotlinx.coroutines.launch
  8. import net.aiterp.git.ykonsole2.application.logging.log
  9. import net.aiterp.git.ykonsole2.application.plugins.ykObjectMapper
  10. import net.aiterp.git.ykonsole2.application.routes.MilestoneDTO
  11. import net.aiterp.git.ykonsole2.application.routes.ValueDTO
  12. import net.aiterp.git.ykonsole2.application.routes.WorkoutDTO
  13. import net.aiterp.git.ykonsole2.domain.models.*
  14. import net.aiterp.git.ykonsole2.domain.runtime.*
  15. import java.lang.Exception
  16. private object WebSocketHandler
  17. fun Route.sockets(
  18. deviceRepo: DeviceRepository,
  19. programRepo: ProgramRepository,
  20. workoutRepo: WorkoutRepository,
  21. workoutStateRepo: WorkoutStateRepository,
  22. commandBus: CommandBus,
  23. eventBus: EventBus,
  24. ) {
  25. val log = WebSocketHandler.log
  26. webSocket("/workouts/active") {
  27. val clientId = "ws-client-${randomId()}"
  28. val active = workoutRepo.findActive()
  29. if (active != null) {
  30. var job: Job? = null
  31. try {
  32. log.info("$clientId: Sending workout metadata...")
  33. val device = deviceRepo.findById(active.deviceId)!!
  34. val program = active.programId?.let { programRepo.findById(it) }
  35. sendSerialized(SocketOutput(workout = WorkoutDTO(active, device, program)))
  36. log.info("$clientId: Sending workout states and milestones...")
  37. val workoutStates = workoutStateRepo.fetchByWorkoutId(active.id)
  38. val mrEvents = workoutStates.makeMilestoneReachedEvents()
  39. sendSerialized(SocketOutput(
  40. workoutStates = workoutStates.map { ValueDTO.from(it) },
  41. oldMilestones = mrEvents.map { MilestoneDTO.from(it) },
  42. ))
  43. job = launch {
  44. log.info("$clientId: Starting event listener...")
  45. eventBus.collect(forceAll = true) { event ->
  46. when (event) {
  47. is Connected, Started, Stopped, Disconnected, Skipped -> {
  48. sendSerialized(SocketOutput(event = SocketOutput.EventDTO(name = event.name)))
  49. if (event is Disconnected) close()
  50. }
  51. is ErrorOccurred -> sendSerialized(SocketOutput(error = SocketOutput.Error(event.message)))
  52. is ValuesReceived -> sendSerialized(SocketOutput(workoutStates = listOf(ValueDTO.from(event.values))))
  53. is MilestoneReached -> sendSerialized(SocketOutput(milestone = MilestoneDTO.from(event)))
  54. }
  55. }
  56. }
  57. log.info("$clientId: Ready for incoming frames")
  58. for (frame in incoming) {
  59. frame as? Frame.Text ?: continue
  60. val input: SocketInput = ykObjectMapper.readValue(frame.readText(), SocketInput::class.java)
  61. input.makeCommands(device).forEach { commandBus.emit(it) }
  62. }
  63. } catch (e: Exception) {
  64. log.info("$clientId: Interrupted by exception: ${e.message}", e)
  65. } finally {
  66. log.info("$clientId: Terminating event listener...")
  67. job?.cancelAndJoin()
  68. log.info("$clientId: Disconnected")
  69. }
  70. } else {
  71. log.info("$clientId: Rejected due to missing workout")
  72. sendSerialized(SocketOutput(error = SocketOutput.Error("No active workout found")))
  73. }
  74. }
  75. }