home · contact · privacy
Move more server interaction logic into former SocketRecvLoop, now ConnectionLoop. master
authorChristian Heller <c.heller@plomlompom.de>
Tue, 10 Jun 2025 06:29:29 +0000 (08:29 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 10 Jun 2025 06:29:29 +0000 (08:29 +0200)
ircplom.py

index f85b62be1dbc2ae80f8e6226b2219a0edc6ab4a8..930b8874f7f1852f57f1dc10be6f1e7b466d9b1e 100755 (executable)
@@ -61,6 +61,7 @@ class EventType(Enum):
     CONN_ALERT = auto()
     CONNECTED = auto()
     CONN_WINDOW = auto()
+    DISCONNECTED = auto()
     EXCEPTION = auto()
     INIT_CONNECT = auto()
     INIT_RECONNECT = auto()
@@ -227,7 +228,7 @@ class IrcConnection:
         self._login = login
         self._socket: Optional[socket] = None
         self._assumed_open = False
-        self._recv_loop: Optional[SocketRecvLoop] = None
+        self._loop: Optional[ConnectionLoop] = None
         self._broadcast(EventType.CONN_WINDOW, self._idx)
         self._start_connecting()
 
@@ -245,18 +246,18 @@ class IrcConnection:
                 return
             self._socket.settimeout(TIMEOUT_LOOP)
             self._assumed_open = True
-            self._broadcast(EventType.CONNECTED)
-            self._recv_loop = SocketRecvLoop(self._idx, self._q_to_main,
-                                             self._read_lines())
+            self._loop = ConnectionLoop(self._idx, self._q_to_main,
+                                        self._read_lines())
+            self._broadcast(EventType.CONNECTED, self._login)
 
         Thread(target=connect, daemon=True, args=(self,)).start()
 
     def close(self):
-        'Close both SocketRecvLoop and socket.'
+        'Close both ConnectionLoop and socket.'
         self._assumed_open = False
-        if self._recv_loop:
-            self._recv_loop.stop()
-        self._recv_loop = None
+        if self._loop:
+            self._loop.stop()
+        self._loop = None
         if self._socket:
             self._socket.close()
         self._socket = None
@@ -306,13 +307,6 @@ class IrcConnection:
 
     def handle(self, event: Event) -> None:
         'Process connection-directed Event into further steps.'
-        if event.type_ == EventType.CONNECTED:
-            self._broadcast(EventType.SEND,
-                            IrcMessage('USER', [self._login[0], '0', '*',
-                                                self._login[2]]))
-            self._broadcast(EventType.SEND,
-                            IrcMessage('NICK', [self._login[1]]))
-            return
         if event.type_ == EventType.INIT_RECONNECT:
             if self._assumed_open:
                 self._broadcast(EventType.CONN_ALERT,
@@ -320,17 +314,14 @@ class IrcConnection:
                                 'so nothing to do.')
             else:
                 self._start_connecting()
-            return
-        msg: IrcMessage = event.payload[1]
-        if event.type_ == EventType.SEND:
+        elif event.type_ == EventType.CONNECTED:
+            assert self._loop is not None
+            self._loop.put(event)
+        elif event.type_ == EventType.DISCONNECTED:
+            self.close()
+        elif event.type_ == EventType.SEND:
+            msg: IrcMessage = event.payload[1]
             self._write_line(msg.raw)
-        elif event.type_ == EventType.RECV:
-            if msg.verb == 'PING':
-                self._broadcast(EventType.SEND,
-                                IrcMessage('PONG', [msg.parameters[0]]))
-            elif msg.verb == 'ERROR'\
-                    and msg.parameters[0].startswith('Closing link:'):
-                self._assumed_open = False
 
 
 class IrcMessage:
@@ -339,12 +330,12 @@ class IrcMessage:
 
     def __init__(self,
                  verb: str,
-                 parameters: Optional[list[str]] = None,
+                 parameters: Optional[tuple[str, ...]] = None,
                  source: str = '',
                  tags: Optional[dict[str, str]] = None
                  ) -> None:
         self.verb: str = verb
-        self.parameters: list[str] = parameters or []
+        self.parameters: tuple[str, ...] = parameters or tuple()
         self.source: str = source
         self.tags: dict[str, str] = tags or {}
 
@@ -372,7 +363,7 @@ class IrcMessage:
                 tags[key] = val
             return tags
 
-        def _split_params(str_params: str) -> list[str]:
+        def _split_params(str_params: str) -> tuple[str, ...]:
             params = []
             params_stage = 0  # 0: gap, 1: non-trailing, 2: trailing
             for char in str_params:
@@ -386,7 +377,7 @@ class IrcMessage:
                         params_stage += 1
                         continue
                 params[-1] += char
-            return params
+            return tuple(p for p in params)
 
         stages = [_Stage('tags', '@', _parse_tags),
                   _Stage('source', ':'),
@@ -744,7 +735,7 @@ class ConnectionWindow(Window):
     def cmd__disconnect(self, quit_msg: str = 'ircplom says bye') -> None:
         'Send QUIT command to server.'
         self._broadcast(EventType.SEND,
-                        (self._conn_idx, IrcMessage('QUIT', [quit_msg])))
+                        (self._conn_idx, IrcMessage('QUIT', (quit_msg, ))))
 
     def cmd__reconnect(self) -> None:
         'Attempt reconnection.'
@@ -898,16 +889,34 @@ class TuiLoop(Loop):
         return None
 
 
-class SocketRecvLoop(Loop):
+class ConnectionLoop(Loop):
     'Loop receiving and translating socket messages towards main loop.'
 
     def __init__(self, connection_idx: int, *args, **kwargs) -> None:
         self._conn_idx = connection_idx
         super().__init__(*args, **kwargs)
 
+    def _send(self, verb: str, parameters: tuple[str, ...]) -> None:
+        self.broadcast(EventType.SEND, (self._conn_idx,
+                                        IrcMessage(verb, parameters)))
+
+    def process_main(self, event: Event) -> bool:
+        if not super().process_main(event):
+            return False
+        if event.type_ == EventType.CONNECTED:
+            login = event.payload[1]
+            self._send('USER', (login[0], '0', '*', login[2]))
+            self._send('NICK', (login[1],))
+        return True
+
     def process_bonus(self, yielded: str) -> None:
-        self.broadcast(EventType.RECV, (self._conn_idx,
-                                        IrcMessage.from_raw(yielded)))
+        msg = IrcMessage.from_raw(yielded)
+        self.broadcast(EventType.RECV, (self._conn_idx, msg))
+        if msg.verb == 'PING':
+            self._send('PONG', (msg.parameters[0],))
+        if msg.verb == 'ERROR'\
+                and msg.parameters[0].startswith('Closing link:'):
+            self.broadcast(EventType.DISCONNECTED, (self._conn_idx,))
 
 
 class KeyboardLoop(Loop):
@@ -953,8 +962,12 @@ def run() -> None:
                     connections += [IrcConnection(q_to_main, len(connections),
                                                   *event.payload)]
                 elif event.type_ in {
-                        EventType.CONNECTED, EventType.INIT_RECONNECT,
-                        EventType.RECV, EventType.SEND}:
+                        EventType.CONNECTED,
+                        EventType.DISCONNECTED,
+                        EventType.INIT_RECONNECT,
+                        EventType.RECV,
+                        EventType.SEND,
+                        }:
                     connections[event.payload[0]].handle(event)
     finally:
         for conn in connections: