Pārlūkot izejas kodu

Better server state handling

Expose status (connecting, negotiating, ready)
Fire ServerReady and ServerConnected events
tags/v0.3.0
Chris Smith 5 gadus atpakaļ
vecāks
revīzija
812ec4990b

+ 2
- 0
CHANGELOG Parādīt failu

7
   * Server state improvements:
7
   * Server state improvements:
8
     * Added status field to ServerState
8
     * Added status field to ServerState
9
     * ServerConnected event is emitted as soon as the socket is connected
9
     * ServerConnected event is emitted as soon as the socket is connected
10
+    * ServerReady event is emitted after logging in, negotiating, etc
11
+  * (Internal) Event handlers can now return more events to emit
10
 
12
 
11
 v0.2.1
13
 v0.2.1
12
 
14
 

+ 3
- 0
src/itest/kotlin/com/dmdirc/irctest/TestRunner.kt Parādīt failu

30
             val clientInput = BufferedReader(InputStreamReader(clientSocket.getInputStream()))
30
             val clientInput = BufferedReader(InputStreamReader(clientSocket.getInputStream()))
31
             val clientOutput = BufferedWriter(OutputStreamWriter(clientSocket.getOutputStream()))
31
             val clientOutput = BufferedWriter(OutputStreamWriter(clientSocket.getOutputStream()))
32
             for (step in test.steps) {
32
             for (step in test.steps) {
33
+                println(step)
33
                 when (step) {
34
                 when (step) {
34
                     is SimpleExpectStep -> {
35
                     is SimpleExpectStep -> {
35
                         while (true) {
36
                         while (true) {
49
                     }
50
                     }
50
                 }
51
                 }
51
             }
52
             }
53
+
54
+            library.terminate()
52
         }
55
         }
53
     }
56
     }
54
 
57
 

+ 7
- 1
src/itest/kotlin/com/dmdirc/ktirc/KtIrcIntegrationTest.kt Parādīt failu

3
 import com.dmdirc.irctest.IrcLibraryTests
3
 import com.dmdirc.irctest.IrcLibraryTests
4
 import com.dmdirc.ktirc.model.Profile
4
 import com.dmdirc.ktirc.model.Profile
5
 import com.dmdirc.ktirc.model.Server
5
 import com.dmdirc.ktirc.model.Server
6
+import kotlinx.coroutines.delay
7
+import kotlinx.coroutines.runBlocking
6
 import org.junit.jupiter.api.TestFactory
8
 import org.junit.jupiter.api.TestFactory
7
 
9
 
8
 class KtIrcIntegrationTest {
10
 class KtIrcIntegrationTest {
18
         }
20
         }
19
 
21
 
20
         override fun terminate() {
22
         override fun terminate() {
21
-            ircClient.disconnect()
23
+            runBlocking {
24
+                delay(100)
25
+                ircClient.disconnect()
26
+                ircClient.join()
27
+            }
22
         }
28
         }
23
 
29
 
24
     })
30
     })

+ 10
- 4
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt Parādīt failu

100
     private var connectionJob: Job? = null
100
     private var connectionJob: Job? = null
101
 
101
 
102
     override fun send(message: String) {
102
     override fun send(message: String) {
103
+        // TODO: What happens if sending fails?
103
         scope.launch {
104
         scope.launch {
104
             socket?.sendLine(message)
105
             socket?.sendLine(message)
105
         }
106
         }
112
                 // TODO: Proper error handling - what if connect() fails?
113
                 // TODO: Proper error handling - what if connect() fails?
113
                 socket = this
114
                 socket = this
114
                 connect()
115
                 connect()
115
-                messageHandler.emitEvent(this@IrcClientImpl, ServerConnected(currentTimeProvider()))
116
-                sendLine("CAP LS 302") // TODO: Stick this in a builder
117
-                server.password?.let { pass -> sendPassword(pass) }
116
+                emitEvent(ServerConnected(currentTimeProvider()))
117
+                sendCapabilityList()
118
+                sendPasswordIfPresent()
118
                 sendNickChange(profile.initialNick)
119
                 sendNickChange(profile.initialNick)
119
                 // TODO: Send correct host
120
                 // TODO: Send correct host
120
                 sendUser(profile.userName, "localhost", server.host, profile.realName)
121
                 sendUser(profile.userName, "localhost", server.host, profile.realName)
133
 
134
 
134
     override fun onEvent(handler: (IrcEvent) -> Unit) {
135
     override fun onEvent(handler: (IrcEvent) -> Unit) {
135
         messageHandler.handlers.add(object : EventHandler {
136
         messageHandler.handlers.add(object : EventHandler {
136
-            override fun processEvent(client: IrcClient, event: IrcEvent) {
137
+            override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
137
                 handler(event)
138
                 handler(event)
139
+                return emptyList()
138
             }
140
             }
139
         })
141
         })
140
     }
142
     }
143
+
144
+    private fun emitEvent(event: IrcEvent) = messageHandler.emitEvent(this, event)
145
+    private fun sendPasswordIfPresent() = server.password?.let(this::sendPassword)
146
+
141
 }
147
 }
142
 
148
 
143
 internal fun main() {
149
 internal fun main() {

+ 2
- 1
src/main/kotlin/com/dmdirc/ktirc/events/CapabilitiesHandler.kt Parādīt failu

12
 
12
 
13
     private val log by logger()
13
     private val log by logger()
14
 
14
 
15
-    override fun processEvent(client: IrcClient, event: IrcEvent) {
15
+    override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
16
         when (event) {
16
         when (event) {
17
             is ServerCapabilitiesReceived -> handleCapabilitiesReceived(client.serverState.capabilities, event.capabilities)
17
             is ServerCapabilitiesReceived -> handleCapabilitiesReceived(client.serverState.capabilities, event.capabilities)
18
             is ServerCapabilitiesFinished -> handleCapabilitiesFinished(client)
18
             is ServerCapabilitiesFinished -> handleCapabilitiesFinished(client)
19
             is ServerCapabilitiesAcknowledged -> handleCapabilitiesAcknowledged(client, event.capabilities)
19
             is ServerCapabilitiesAcknowledged -> handleCapabilitiesAcknowledged(client, event.capabilities)
20
         }
20
         }
21
+        return emptyList()
21
     }
22
     }
22
 
23
 
23
     private fun handleCapabilitiesReceived(state: CapabilitiesState, capabilities: Map<Capability, String>) {
24
     private fun handleCapabilitiesReceived(state: CapabilitiesState, capabilities: Map<Capability, String>) {

+ 2
- 1
src/main/kotlin/com/dmdirc/ktirc/events/ChannelStateHandler.kt Parādīt failu

9
 
9
 
10
     private val log by logger()
10
     private val log by logger()
11
 
11
 
12
-    override fun processEvent(client: IrcClient, event: IrcEvent) {
12
+    override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
13
         when (event) {
13
         when (event) {
14
             is ChannelJoined -> handleJoin(client, event)
14
             is ChannelJoined -> handleJoin(client, event)
15
             is ChannelParted -> handlePart(client, event)
15
             is ChannelParted -> handlePart(client, event)
17
             is ChannelNamesFinished -> handleNamesFinished(client, event)
17
             is ChannelNamesFinished -> handleNamesFinished(client, event)
18
             is UserQuit -> handleQuit(client, event)
18
             is UserQuit -> handleQuit(client, event)
19
         }
19
         }
20
+        return emptyList()
20
     }
21
     }
21
 
22
 
22
     private fun handleJoin(client: IrcClient, event: ChannelJoined) {
23
     private fun handleJoin(client: IrcClient, event: ChannelJoined) {

+ 1
- 1
src/main/kotlin/com/dmdirc/ktirc/events/EventHandler.kt Parādīt failu

5
 @FunctionalInterface
5
 @FunctionalInterface
6
 internal interface EventHandler {
6
 internal interface EventHandler {
7
 
7
 
8
-    fun processEvent(client: IrcClient, event: IrcEvent)
8
+    fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent>
9
 
9
 
10
 }
10
 }
11
 
11
 

+ 3
- 0
src/main/kotlin/com/dmdirc/ktirc/events/Events.kt Parādīt failu

11
 /** Raised when the connection to the server has been established. The server will not be ready for use yet. */
11
 /** Raised when the connection to the server has been established. The server will not be ready for use yet. */
12
 class ServerConnected(time: LocalDateTime) : IrcEvent(time)
12
 class ServerConnected(time: LocalDateTime) : IrcEvent(time)
13
 
13
 
14
+/** Raised when the server is ready for use. */
15
+class ServerReady(time: LocalDateTime) : IrcEvent(time)
16
+
14
 /** Raised when the server initially welcomes us to the IRC network. */
17
 /** Raised when the server initially welcomes us to the IRC network. */
15
 class ServerWelcome(time: LocalDateTime, val localNick: String) : IrcEvent(time)
18
 class ServerWelcome(time: LocalDateTime, val localNick: String) : IrcEvent(time)
16
 
19
 

+ 2
- 1
src/main/kotlin/com/dmdirc/ktirc/events/PingHandler.kt Parādīt failu

5
 
5
 
6
 internal class PingHandler : EventHandler {
6
 internal class PingHandler : EventHandler {
7
 
7
 
8
-    override fun processEvent(client: IrcClient, event: IrcEvent) {
8
+    override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
9
         when (event) {
9
         when (event) {
10
             is PingReceived -> client.sendPong(event.nonce)
10
             is PingReceived -> client.sendPong(event.nonce)
11
         }
11
         }
12
+        return emptyList()
12
     }
13
     }
13
 
14
 
14
 }
15
 }

+ 28
- 2
src/main/kotlin/com/dmdirc/ktirc/events/ServerStateHandler.kt Parādīt failu

1
 package com.dmdirc.ktirc.events
1
 package com.dmdirc.ktirc.events
2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.model.ServerState
5
+import com.dmdirc.ktirc.model.ServerStatus
6
+import java.time.LocalDateTime
4
 
7
 
5
 internal class ServerStateHandler : EventHandler {
8
 internal class ServerStateHandler : EventHandler {
6
 
9
 
7
-    override fun processEvent(client: IrcClient, event: IrcEvent) {
10
+    override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
8
         when (event) {
11
         when (event) {
9
-            is ServerWelcome -> client.serverState.localNickname = event.localNick
12
+            is ServerConnected -> client.serverState.status = ServerStatus.Negotiating
13
+            is ServerWelcome -> handleWelcome(client.serverState, event.localNick)
10
             is ServerFeaturesUpdated -> client.serverState.features.setAll(event.serverFeatures)
14
             is ServerFeaturesUpdated -> client.serverState.features.setAll(event.serverFeatures)
15
+
16
+            // Events that won't trigger a server ready event
17
+            is PingReceived -> Unit
18
+            is ServerCapabilitiesReceived -> Unit
19
+            is ServerCapabilitiesAcknowledged -> Unit
20
+            is ServerCapabilitiesFinished -> Unit
21
+
22
+            else -> return checkReadyState(client, event.time)
23
+        }
24
+        return emptyList()
25
+    }
26
+
27
+    private fun handleWelcome(serverState: ServerState, localNick: String) {
28
+        serverState.receivedWelcome = true
29
+        serverState.localNickname = localNick
30
+    }
31
+
32
+    private fun checkReadyState(client: IrcClient, time: LocalDateTime): List<IrcEvent> {
33
+        if (client.serverState.receivedWelcome && client.serverState.status == ServerStatus.Negotiating) {
34
+            client.serverState.status = ServerStatus.Ready
35
+            return listOf(ServerReady(time))
11
         }
36
         }
37
+        return emptyList()
12
     }
38
     }
13
 
39
 
14
 }
40
 }

+ 2
- 1
src/main/kotlin/com/dmdirc/ktirc/events/UserStateHandler.kt Parādīt failu

5
 
5
 
6
 internal class UserStateHandler : EventHandler {
6
 internal class UserStateHandler : EventHandler {
7
 
7
 
8
-    override fun processEvent(client: IrcClient, event: IrcEvent) {
8
+    override fun processEvent(client: IrcClient, event: IrcEvent): List<IrcEvent> {
9
         when (event) {
9
         when (event) {
10
             is ChannelJoined -> handleJoin(client.userState, event)
10
             is ChannelJoined -> handleJoin(client.userState, event)
11
             is ChannelParted -> handlePart(client, event)
11
             is ChannelParted -> handlePart(client, event)
12
             is ChannelNamesReceived  -> handleNamesReceived(client, event)
12
             is ChannelNamesReceived  -> handleNamesReceived(client, event)
13
             is UserQuit -> handleQuit(client.userState, event)
13
             is UserQuit -> handleQuit(client.userState, event)
14
         }
14
         }
15
+        return emptyList()
15
     }
16
     }
16
 
17
 
17
     private fun handleJoin(state: UserState, event: ChannelJoined) {
18
     private fun handleJoin(state: UserState, event: ChannelJoined) {

+ 3
- 1
src/main/kotlin/com/dmdirc/ktirc/io/MessageHandler.kt Parādīt failu

20
 
20
 
21
     fun emitEvent(ircClient: IrcClient, ircEvent: IrcEvent) {
21
     fun emitEvent(ircClient: IrcClient, ircEvent: IrcEvent) {
22
         handlers.forEach { handler ->
22
         handlers.forEach { handler ->
23
-            handler.processEvent(ircClient, ircEvent)
23
+            handler.processEvent(ircClient, ircEvent).forEach {
24
+                emitEvent(ircClient, it)
25
+            }
24
         }
26
         }
25
     }
27
     }
26
 
28
 

+ 2
- 0
src/main/kotlin/com/dmdirc/ktirc/messages/MessageBuilders.kt Parādīt failu

2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 
4
 
5
+/** Sends a message to ask the server to list capabilities. */
6
+internal fun IrcClient.sendCapabilityList() = send("CAP LS 302")
5
 /** Sends a message indicating the end of capability negotiation. */
7
 /** Sends a message indicating the end of capability negotiation. */
6
 internal fun IrcClient.sendCapabilityEnd() = send("CAP END")
8
 internal fun IrcClient.sendCapabilityEnd() = send("CAP END")
7
 /** Sends a message requesting the specified caps are enabled. */
9
 /** Sends a message requesting the specified caps are enabled. */

+ 4
- 1
src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt Parādīt failu

8
  */
8
  */
9
 class ServerState internal constructor(initialNickname: String) {
9
 class ServerState internal constructor(initialNickname: String) {
10
 
10
 
11
+    /** Whether we've received the 'Welcome to IRC' (001) message. */
12
+    internal var receivedWelcome = false
13
+
11
     /** The current status of the server. */
14
     /** The current status of the server. */
12
     var status = ServerStatus.Connecting
15
     var status = ServerStatus.Connecting
13
         internal set
16
         internal set
72
     Negotiating,
75
     Negotiating,
73
     /** We are connected and commands can be sent. */
76
     /** We are connected and commands can be sent. */
74
     Ready,
77
     Ready,
75
-}
78
+}

+ 52
- 3
src/test/kotlin/com/dmdirc/ktirc/events/ServerStateHandlerTest.kt Parādīt failu

2
 
2
 
3
 import com.dmdirc.ktirc.IrcClient
3
 import com.dmdirc.ktirc.IrcClient
4
 import com.dmdirc.ktirc.TestConstants
4
 import com.dmdirc.ktirc.TestConstants
5
-import com.dmdirc.ktirc.model.ServerFeature
6
-import com.dmdirc.ktirc.model.ServerFeatureMap
7
-import com.dmdirc.ktirc.model.ServerState
5
+import com.dmdirc.ktirc.model.*
8
 import com.nhaarman.mockitokotlin2.doReturn
6
 import com.nhaarman.mockitokotlin2.doReturn
9
 import com.nhaarman.mockitokotlin2.mock
7
 import com.nhaarman.mockitokotlin2.mock
10
 import kotlinx.coroutines.runBlocking
8
 import kotlinx.coroutines.runBlocking
11
 import org.junit.jupiter.api.Assertions.assertEquals
9
 import org.junit.jupiter.api.Assertions.assertEquals
10
+import org.junit.jupiter.api.Assertions.assertTrue
12
 import org.junit.jupiter.api.Test
11
 import org.junit.jupiter.api.Test
13
 
12
 
14
 internal class ServerStateHandlerTest {
13
 internal class ServerStateHandlerTest {
26
         assertEquals("acidBurn", serverState.localNickname)
25
         assertEquals("acidBurn", serverState.localNickname)
27
     }
26
     }
28
 
27
 
28
+    @Test
29
+    fun `ServerStateHandler sets receivedWelcome on welcome event`() = runBlocking {
30
+        handler.processEvent(ircClient, ServerWelcome(TestConstants.time, "acidBurn"))
31
+        assertTrue(serverState.receivedWelcome)
32
+    }
33
+
34
+    @Test
35
+    fun `ServerStateHandler sets state to negotiating on connected`() = runBlocking {
36
+        handler.processEvent(ircClient, ServerConnected(TestConstants.time))
37
+        assertEquals(ServerStatus.Negotiating, serverState.status)
38
+    }
39
+
40
+    @Test
41
+    fun `ServerStateHandler sets server state to ready on receiving post-005 line`() = runBlocking {
42
+        ircClient.serverState.status = ServerStatus.Negotiating
43
+
44
+        listOf(
45
+                ServerWelcome(TestConstants.time, "acidBurn"),
46
+                PingReceived(TestConstants.time, "1234".toByteArray()),
47
+                ServerCapabilitiesReceived(TestConstants.time, emptyMap()),
48
+                ServerCapabilitiesAcknowledged(TestConstants.time, emptyMap()),
49
+                ServerCapabilitiesFinished(TestConstants.time),
50
+                MessageReceived(TestConstants.time, User("zeroCool"), "acidBurn", "Welcome!")
51
+        ).forEach {
52
+            assertEquals(ServerStatus.Negotiating, serverState.status)
53
+            handler.processEvent(ircClient, it)
54
+        }
55
+
56
+        assertEquals(ServerStatus.Ready, serverState.status)
57
+    }
58
+
59
+    @Test
60
+    fun `ServerStateHandler emits event on receiving post-005 line`() = runBlocking {
61
+        ircClient.serverState.status = ServerStatus.Negotiating
62
+
63
+        listOf(
64
+                ServerWelcome(TestConstants.time, "acidBurn"),
65
+                PingReceived(TestConstants.time, "1234".toByteArray()),
66
+                ServerCapabilitiesReceived(TestConstants.time, emptyMap()),
67
+                ServerCapabilitiesAcknowledged(TestConstants.time, emptyMap()),
68
+                ServerCapabilitiesFinished(TestConstants.time)
69
+        ).forEach {
70
+            assertTrue(handler.processEvent(ircClient, it).isEmpty())
71
+        }
72
+
73
+        val events = handler.processEvent(ircClient, MessageReceived(TestConstants.time, User("zeroCool"), "acidBurn", "Welcome!"))
74
+        assertEquals(1, events.size)
75
+        assertTrue(events[0] is ServerReady)
76
+    }
77
+
29
     @Test
78
     @Test
30
     fun `ServerStateHandler updates features on features event`() = runBlocking {
79
     fun `ServerStateHandler updates features on features event`() = runBlocking {
31
         val features = ServerFeatureMap()
80
         val features = ServerFeatureMap()

+ 16
- 2
src/test/kotlin/com/dmdirc/ktirc/io/MessageHandlerTest.kt Parādīt failu

4
 import com.dmdirc.ktirc.TestConstants
4
 import com.dmdirc.ktirc.TestConstants
5
 import com.dmdirc.ktirc.events.EventHandler
5
 import com.dmdirc.ktirc.events.EventHandler
6
 import com.dmdirc.ktirc.events.ServerConnected
6
 import com.dmdirc.ktirc.events.ServerConnected
7
+import com.dmdirc.ktirc.events.ServerReady
7
 import com.dmdirc.ktirc.events.ServerWelcome
8
 import com.dmdirc.ktirc.events.ServerWelcome
8
 import com.dmdirc.ktirc.messages.MessageProcessor
9
 import com.dmdirc.ktirc.messages.MessageProcessor
9
 import com.dmdirc.ktirc.model.IrcMessage
10
 import com.dmdirc.ktirc.model.IrcMessage
60
     }
61
     }
61
 
62
 
62
     @Test
63
     @Test
63
-    fun `MessageHandler invokes all event handler with all returned events`() = runBlocking {
64
+    fun `MessageHandler invokes all event handler with all returned events`() = runBlocking<Unit> {
64
         val eventHandler1 = mock<EventHandler>()
65
         val eventHandler1 = mock<EventHandler>()
65
         val eventHandler2 = mock<EventHandler>()
66
         val eventHandler2 = mock<EventHandler>()
66
         val handler = MessageHandler(listOf(joinProcessor, nickProcessor), mutableListOf(eventHandler1, eventHandler2))
67
         val handler = MessageHandler(listOf(joinProcessor, nickProcessor), mutableListOf(eventHandler1, eventHandler2))
80
     }
81
     }
81
 
82
 
82
     @Test
83
     @Test
83
-    fun `MessageHandler emits custom events to all handlers`() = runBlocking {
84
+    fun `MessageHandler emits custom events to all handlers`() = runBlocking<Unit> {
84
         val eventHandler1 = mock<EventHandler>()
85
         val eventHandler1 = mock<EventHandler>()
85
         val eventHandler2 = mock<EventHandler>()
86
         val eventHandler2 = mock<EventHandler>()
86
         val handler = MessageHandler(emptyList(), mutableListOf(eventHandler1, eventHandler2))
87
         val handler = MessageHandler(emptyList(), mutableListOf(eventHandler1, eventHandler2))
90
         verify(eventHandler2).processEvent(same(ircClient), isA<ServerWelcome>())
91
         verify(eventHandler2).processEvent(same(ircClient), isA<ServerWelcome>())
91
     }
92
     }
92
 
93
 
94
+    @Test
95
+    fun `MessageHandler emits events returned from handler`() = runBlocking<Unit> {
96
+        val eventHandler1 = mock<EventHandler> {
97
+            on { processEvent(any(), isA<ServerWelcome>()) } doReturn listOf(ServerReady(TestConstants.time))
98
+        }
99
+        val eventHandler2 = mock<EventHandler>()
100
+        val handler = MessageHandler(emptyList(), mutableListOf(eventHandler1, eventHandler2))
101
+        handler.emitEvent(ircClient, ServerWelcome(TestConstants.time, "abc"))
102
+
103
+        verify(eventHandler1).processEvent(same(ircClient), isA<ServerReady>())
104
+        verify(eventHandler2).processEvent(same(ircClient), isA<ServerReady>())
105
+    }
106
+
93
 }
107
 }

Notiek ielāde…
Atcelt
Saglabāt