Browse Source

More work on async methods

tags/v0.11.0
Chris Smith 5 years ago
parent
commit
3289bb8e99

+ 17
- 8
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

@@ -3,6 +3,7 @@ package com.dmdirc.ktirc
3 3
 import com.dmdirc.ktirc.events.IrcEvent
4 4
 import com.dmdirc.ktirc.io.CaseMapping
5 5
 import com.dmdirc.ktirc.messages.sendJoin
6
+import com.dmdirc.ktirc.messages.tagMap
6 7
 import com.dmdirc.ktirc.model.*
7 8
 import com.dmdirc.ktirc.util.RemoveIn
8 9
 import kotlinx.coroutines.Deferred
@@ -143,15 +144,19 @@ internal interface ExperimentalIrcClient : IrcClient {
143 144
      * This should only be needed to send raw/custom commands; standard messages can be sent using the
144 145
      * extension methods in [com.dmdirc.ktirc.messages] such as TODO: sendJoinAsync.
145 146
      *
146
-     * This method will return immediately. If the server supports the labeled-responses capability,
147
-     * the returned [Deferred] will be eventually populated with the response from the server. If
148
-     * the server does not support the capability, or the response times out, `null` will be supplied.
147
+     * This method will return immediately. The returned [Deferred] will eventually be populated with
148
+     * the server's response. If the server supports the labeled-responses capability, a label will
149
+     * be added to the outgoing message to identify the correct response; otherwise the [matcher]
150
+     * will be invoked on all incoming events to select the appropriate response.
151
+     *
152
+     * If the response times out, `null` will be supplied instead of an event.
149 153
      *
150 154
      * @param command The command to be sent.
151 155
      * @param arguments The arguments to the command.
156
+     * @param matcher The matcher to use to find a matching event.
152 157
      * @return A deferred [IrcEvent]? that contains the server's response to the command.
153 158
      */
154
-    fun sendAsync(command: String, vararg arguments: String) = sendAsync(emptyMap(),  command, *arguments)
159
+    fun sendAsync(command: String, arguments: Array<String>, matcher: (IrcEvent) -> Boolean) = sendAsync(tagMap(), command, arguments, matcher)
155 160
 
156 161
     /**
157 162
      * Sends the given command to the IRC server, and waits for a response back.
@@ -159,16 +164,20 @@ internal interface ExperimentalIrcClient : IrcClient {
159 164
      * This should only be needed to send raw/custom commands; standard messages can be sent using the
160 165
      * extension methods in [com.dmdirc.ktirc.messages] such as TODO: sendJoinAsync.
161 166
      *
162
-     * This method will return immediately. If the server supports the labeled-responses capability,
163
-     * the returned [Deferred] will be eventually populated with the response from the server. If
164
-     * the server does not support the capability, or the response times out, `null` will be supplied.
167
+     * This method will return immediately. The returned [Deferred] will eventually be populated with
168
+     * the server's response. If the server supports the labeled-responses capability, a label will
169
+     * be added to the outgoing message to identify the correct response; otherwise the [matcher]
170
+     * will be invoked on all incoming events to select the appropriate response.
171
+     *
172
+     * If the response times out, `null` will be supplied instead of an event.
165 173
      *
166 174
      * @param tags The IRCv3 tags to prefix the message with, if any.
167 175
      * @param command The command to be sent.
168 176
      * @param arguments The arguments to the command.
177
+     * @param matcher The matcher to use to find a matching event.
169 178
      * @return A deferred [IrcEvent]? that contains the server's response to the command.
170 179
      */
171
-    fun sendAsync(tags: Map<MessageTag, String>, command: String, vararg arguments: String): Deferred<IrcEvent?>
180
+    fun sendAsync(tags: Map<MessageTag, String>, command: String, arguments: Array<String>, matcher: (IrcEvent) -> Boolean): Deferred<IrcEvent?>
172 181
 
173 182
 }
174 183
 

+ 11
- 7
src/main/kotlin/com/dmdirc/ktirc/IrcClientImpl.kt View File

@@ -64,17 +64,21 @@ internal class IrcClientImpl(private val config: IrcClientConfig) : Experimental
64 64
                 ?: log.warning { "No send channel for command: $command" }
65 65
     }
66 66
 
67
-    override fun sendAsync(tags: Map<MessageTag, String>, command: String, vararg arguments: String) = async {
68
-        if (serverState.supportsLabeledResponses) {
69
-            val label = generateLabel(this@IrcClientImpl)
70
-            val channel = Channel<IrcEvent>(1)
71
-            serverState.labelChannels[label] = channel
67
+    override fun sendAsync(tags: Map<MessageTag, String>, command: String, arguments: Array<String>, matcher: (IrcEvent) -> Boolean) = async {
68
+        val label = generateLabel(this@IrcClientImpl)
69
+        val channel = Channel<IrcEvent>(1)
70
+
71
+        if (serverState.asyncResponseState.supportsLabeledResponses) {
72
+            serverState.asyncResponseState.pendingResponses[label] = channel to { event -> event.metadata.label == label }
72 73
             send(tags + (MessageTag.Label to label), command, *arguments)
73
-            withTimeoutOrNull(asyncTimeout) { channel.receive() }.also { serverState.labelChannels.remove(label) }
74 74
         } else {
75
+            serverState.asyncResponseState.pendingResponses[label] = channel to matcher
75 76
             send(tags, command, *arguments)
76
-            null
77 77
         }
78
+
79
+        withTimeoutOrNull(asyncTimeout) {
80
+            channel.receive()
81
+        }.also { serverState.asyncResponseState.pendingResponses.remove(label) }
78 82
     }
79 83
 
80 84
     override fun connect() {

+ 7
- 5
src/main/kotlin/com/dmdirc/ktirc/events/handlers/LabelledResponseHandler.kt View File

@@ -8,11 +8,13 @@ import kotlinx.coroutines.launch
8 8
 internal class LabelledResponseHandler : EventHandler {
9 9
 
10 10
     override fun processEvent(client: IrcClient, event: IrcEvent) {
11
-        event.metadata.label?.let {
12
-            GlobalScope.launch {
13
-                client.serverState.labelChannels[it]?.send(event)
14
-            }
15
-        }
11
+        client.serverState.asyncResponseState.pendingResponses.values
12
+                .filter { it.second(event) }
13
+                .forEach {
14
+                    GlobalScope.launch {
15
+                        it.first.send(event)
16
+                    }
17
+                }
16 18
     }
17 19
 
18 20
 }

+ 28
- 14
src/main/kotlin/com/dmdirc/ktirc/model/ServerState.kt View File

@@ -73,20 +73,9 @@ class ServerState internal constructor(
73 73
     internal val batches = mutableMapOf<String, Batch>()
74 74
 
75 75
     /**
76
-     * Counter for ensuring sent labels are unique.
77
-     */
78
-    internal val labelCounter = AtomicLong(0)
79
-
80
-    /**
81
-     * Whether or not the server supports labeled responses.
82
-     */
83
-    internal val supportsLabeledResponses: Boolean
84
-        get() = Capability.LabeledResponse in capabilities.enabledCapabilities
85
-
86
-    /**
87
-     * Channels waiting for a label to be received.
76
+     * Asynchronous command state.
88 77
      */
89
-    internal val labelChannels = mutableMapOf<String, SendChannel<IrcEvent>>()
78
+    internal val asyncResponseState = AsyncResponseState(capabilities)
90 79
 
91 80
     /**
92 81
      * Determines if the given mode is one applied to a user of a channel, such as 'o' for operator.
@@ -118,8 +107,33 @@ class ServerState internal constructor(
118 107
         capabilities.reset()
119 108
         sasl.reset()
120 109
         batches.clear()
110
+        asyncResponseState.reset()
111
+    }
112
+
113
+}
114
+
115
+internal class AsyncResponseState(private val capabilities : CapabilitiesState) {
116
+
117
+
118
+    /**
119
+     * Counter for ensuring sent labels are unique.
120
+     */
121
+    internal val labelCounter = AtomicLong(0)
122
+
123
+    /**
124
+     * Whether or not the server supports labeled responses.
125
+     */
126
+    internal val supportsLabeledResponses: Boolean
127
+        get() = Capability.LabeledResponse in capabilities.enabledCapabilities
128
+
129
+    /**
130
+     * Channels waiting for a response to be received.
131
+     */
132
+    internal val pendingResponses = mutableMapOf<String, Pair<SendChannel<IrcEvent>, (IrcEvent) -> Boolean>>()
133
+
134
+    internal fun reset() {
121 135
         labelCounter.set(0)
122
-        labelChannels.clear()
136
+        pendingResponses.clear()
123 137
     }
124 138
 
125 139
 }

+ 1
- 1
src/main/kotlin/com/dmdirc/ktirc/util/Labels.kt View File

@@ -8,7 +8,7 @@ internal var generateLabel: (IrcClient) -> String = ::defaultGenerateLabel
8 8
 
9 9
 internal fun defaultGenerateLabel(ircClient: IrcClient): String {
10 10
     val time = currentTimeProvider().toEpochSecond(ZoneOffset.UTC)
11
-    val counter = ircClient.serverState.labelCounter.incrementAndGet()
11
+    val counter = ircClient.serverState.asyncResponseState.labelCounter.incrementAndGet()
12 12
     return ByteArray(6) {
13 13
         when {
14 14
             it < 3 -> (time shr it and 0xff).toByte()

+ 3
- 3
src/test/kotlin/com/dmdirc/ktirc/IrcClientImplTest.kt View File

@@ -294,7 +294,7 @@ internal class IrcClientImplTest {
294 294
         client.socketFactory = mockSocketFactory
295 295
         client.connect()
296 296
 
297
-        client.sendAsync(tagMap(), "testing", "123")
297
+        client.sendAsync(tagMap(), "testing", arrayOf("123")) { false }
298 298
 
299 299
         assertLineReceived("testing 123")
300 300
     }
@@ -307,7 +307,7 @@ internal class IrcClientImplTest {
307 307
         client.serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
308 308
         client.connect()
309 309
 
310
-        client.sendAsync(tagMap(), "testing", "123")
310
+        client.sendAsync(tagMap(), "testing", arrayOf("123")) { false }
311 311
 
312 312
         assertLineReceived("@draft/label=abc123 testing 123")
313 313
     }
@@ -320,7 +320,7 @@ internal class IrcClientImplTest {
320 320
         client.serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
321 321
         client.connect()
322 322
 
323
-        client.sendAsync(tagMap(MessageTag.AccountName to "x"), "testing", "123")
323
+        client.sendAsync(tagMap(MessageTag.AccountName to "x"), "testing", arrayOf("123")) { false }
324 324
 
325 325
         assertLineReceived("@account=x;draft/label=abc123 testing 123")
326 326
     }

+ 7
- 6
src/test/kotlin/com/dmdirc/ktirc/model/ServerStateTest.kt View File

@@ -2,6 +2,7 @@ package com.dmdirc.ktirc.model
2 2
 
3 3
 import com.dmdirc.ktirc.TestConstants
4 4
 import com.dmdirc.ktirc.events.EventMetadata
5
+import com.dmdirc.ktirc.events.IrcEvent
5 6
 import kotlinx.coroutines.channels.Channel
6 7
 import org.junit.jupiter.api.Assertions.*
7 8
 import org.junit.jupiter.api.Test
@@ -65,13 +66,13 @@ internal class ServerStateTest {
65 66
     fun `indicates labels are enabled when cap is present`() {
66 67
         val serverState = ServerState("acidBurn", "")
67 68
         serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
68
-        assertTrue(serverState.supportsLabeledResponses)
69
+        assertTrue(serverState.asyncResponseState.supportsLabeledResponses)
69 70
     }
70 71
 
71 72
     @Test
72 73
     fun `indicates labels are not enabled when cap is absent`() {
73 74
         val serverState = ServerState("acidBurn", "")
74
-        assertFalse(serverState.supportsLabeledResponses)
75
+        assertFalse(serverState.asyncResponseState.supportsLabeledResponses)
75 76
     }
76 77
 
77 78
     @Test
@@ -84,8 +85,8 @@ internal class ServerStateTest {
84 85
         capabilities.advertisedCapabilities["sasl"] = "sure"
85 86
         sasl.saslBuffer = "in progress"
86 87
         batches["batch"] = Batch("type", emptyList(), EventMetadata(TestConstants.time))
87
-        labelCounter.set(100)
88
-        labelChannels["#thegibson"] = Channel(1)
88
+        asyncResponseState.labelCounter.set(100)
89
+        asyncResponseState.pendingResponses["#thegibson"] = Pair<Channel<IrcEvent>, (IrcEvent) -> Boolean>(Channel(1)) { false }
89 90
 
90 91
         reset()
91 92
 
@@ -97,8 +98,8 @@ internal class ServerStateTest {
97 98
         assertTrue(capabilities.advertisedCapabilities.isEmpty())
98 99
         assertEquals("", sasl.saslBuffer)
99 100
         assertTrue(batches.isEmpty())
100
-        assertEquals(0, labelCounter.get())
101
-        assertTrue(labelChannels.isEmpty())
101
+        assertEquals(0, asyncResponseState.labelCounter.get())
102
+        assertTrue(asyncResponseState.pendingResponses.isEmpty())
102 103
     }
103 104
 
104 105
 }

+ 4
- 4
src/test/kotlin/com/dmdirc/ktirc/util/LabelsTest.kt View File

@@ -18,11 +18,11 @@ internal class LabelsTest {
18 18
 
19 19
     @Test
20 20
     fun `increments the label id when generating labels`() {
21
-        assertEquals(0L, fakeServerState.labelCounter.get())
21
+        assertEquals(0L, fakeServerState.asyncResponseState.labelCounter.get())
22 22
         defaultGenerateLabel(ircClient)
23
-        assertEquals(1L, fakeServerState.labelCounter.get())
23
+        assertEquals(1L, fakeServerState.asyncResponseState.labelCounter.get())
24 24
         defaultGenerateLabel(ircClient)
25
-        assertEquals(2L, fakeServerState.labelCounter.get())
25
+        assertEquals(2L, fakeServerState.asyncResponseState.labelCounter.get())
26 26
     }
27 27
 
28 28
     @Test
@@ -36,7 +36,7 @@ internal class LabelsTest {
36 36
     fun `generates unique labels at different times with the same counter value`() {
37 37
         currentTimeProvider = { TestConstants.time }
38 38
         val label1 = defaultGenerateLabel(ircClient)
39
-        fakeServerState.labelCounter.set(0L)
39
+        fakeServerState.asyncResponseState.labelCounter.set(0L)
40 40
         currentTimeProvider = { TestConstants.otherTime }
41 41
         val label2 = defaultGenerateLabel(ircClient)
42 42
         assertNotEquals(label1, label2)

Loading…
Cancel
Save