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.5KB

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