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.

IrcClientImpl.kt 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package com.dmdirc.ktirc
  2. import com.dmdirc.ktirc.events.*
  3. import com.dmdirc.ktirc.events.handlers.EventHandler
  4. import com.dmdirc.ktirc.events.handlers.eventHandlers
  5. import com.dmdirc.ktirc.events.mutators.eventMutators
  6. import com.dmdirc.ktirc.io.KtorLineBufferedSocket
  7. import com.dmdirc.ktirc.io.LineBufferedSocket
  8. import com.dmdirc.ktirc.io.MessageHandler
  9. import com.dmdirc.ktirc.io.MessageParser
  10. import com.dmdirc.ktirc.messages.*
  11. import com.dmdirc.ktirc.model.ChannelStateMap
  12. import com.dmdirc.ktirc.model.ServerState
  13. import com.dmdirc.ktirc.model.UserState
  14. import com.dmdirc.ktirc.model.toConnectionError
  15. import com.dmdirc.ktirc.util.currentTimeProvider
  16. import com.dmdirc.ktirc.util.logger
  17. import io.ktor.util.KtorExperimentalAPI
  18. import kotlinx.coroutines.*
  19. import kotlinx.coroutines.channels.map
  20. import java.util.concurrent.atomic.AtomicBoolean
  21. /**
  22. * Concrete implementation of an [IrcClient].
  23. */
  24. // TODO: How should alternative nicknames work?
  25. // TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
  26. // TODO: Should there be a default profile?
  27. internal class IrcClientImpl(private val config: IrcClientConfig) : IrcClient, CoroutineScope {
  28. private val log by logger()
  29. @ExperimentalCoroutinesApi
  30. override val coroutineContext = GlobalScope.newCoroutineContext(Dispatchers.IO)
  31. @ExperimentalCoroutinesApi
  32. @KtorExperimentalAPI
  33. internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::KtorLineBufferedSocket
  34. override var behaviour = config.behaviour
  35. override val serverState = ServerState(config.profile.nickname, config.server.host, config.sasl)
  36. override val channelState = ChannelStateMap { caseMapping }
  37. override val userState = UserState { caseMapping }
  38. private val messageHandler = MessageHandler(messageProcessors, eventMutators, eventHandlers.toMutableList())
  39. private val parser = MessageParser()
  40. private var socket: LineBufferedSocket? = null
  41. private val connecting = AtomicBoolean(false)
  42. override fun send(message: String) {
  43. socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message" }
  44. }
  45. override fun connect() {
  46. check(!connecting.getAndSet(true))
  47. @Suppress("EXPERIMENTAL_API_USAGE")
  48. with(socketFactory(this, config.server.host, config.server.port, config.server.useTls)) {
  49. socket = this
  50. emitEvent(ServerConnecting(currentTimeProvider()))
  51. launch {
  52. try {
  53. connect()
  54. emitEvent(ServerConnected(currentTimeProvider()))
  55. sendCapabilityList()
  56. sendPasswordIfPresent()
  57. sendNickChange(config.profile.nickname)
  58. sendUser(config.profile.username, config.profile.realName)
  59. messageHandler.processMessages(this@IrcClientImpl, receiveChannel.map { parser.parse(it) })
  60. } catch (ex : Exception) {
  61. emitEvent(ServerConnectionError(currentTimeProvider(), ex.toConnectionError(), ex.localizedMessage))
  62. }
  63. reset()
  64. emitEvent(ServerDisconnected(currentTimeProvider()))
  65. }
  66. }
  67. }
  68. override fun disconnect() {
  69. socket?.disconnect()
  70. }
  71. override fun onEvent(handler: (IrcEvent) -> Unit) {
  72. messageHandler.handlers.add(object : EventHandler {
  73. override fun processEvent(client: IrcClient, event: IrcEvent) {
  74. handler(event)
  75. }
  76. })
  77. }
  78. private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
  79. private fun sendPasswordIfPresent() = config.server.password?.let(this::sendPassword)
  80. internal fun reset() {
  81. serverState.reset()
  82. channelState.clear()
  83. userState.reset()
  84. socket = null
  85. connecting.set(false)
  86. }
  87. }