home · contact · privacy
Make curses client capable of websocket _and_ raw tcp connections.
authorChristian Heller <c.heller@plomlompom.de>
Sat, 7 Nov 2020 23:42:35 +0000 (00:42 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Sat, 7 Nov 2020 23:42:35 +0000 (00:42 +0100)
new2/plomrogue/io_tcp.py
new2/requirements.txt
new2/rogue_chat_curses.py

index f0a49a97dad632fa4f3dc10770432f424ba90155..b030f1b9f1c98332084763812bf152666004bb7a 100644 (file)
@@ -6,6 +6,7 @@ socketserver.TCPServer.allow_reuse_address = True
 
 
 
+from plomrogue.errors import BrokenSocketConnection
 class PlomSocket:
 
     def __init__(self, socket):
@@ -32,7 +33,6 @@ class PlomSocket:
         <http://stackoverflow.com/q/34919846>
 
         """
-        from plomrogue.errors import BrokenSocketConnection
         escaped_message = ''
         for char in message:
             if char in ('\\', '$'):
@@ -77,7 +77,11 @@ class PlomSocket:
         data = b''
         msg = b''
         while True:
-            data = self.socket.recv(1024)
+            try:
+                data = self.socket.recv(1024)
+            except OSError as err:
+                if err.errno == 9:  # "Bad file descriptor", when connection broken
+                    raise BrokenSocketConnection
             if 0 == len(data):
                 break
             for c in data:
index 4cf00d1f6f4f1a97c21ac1f200d51e4848d92b5f..7d7d093cc3be48c2d5ab27a81e9a634c703d2f22 100644 (file)
@@ -1 +1,2 @@
 SimpleWebSocketServer
+ws4py
index bb3dac263e84670c8f9ef851f8b87844d6b9e4a6..689ecc1b1686f023799ae7f94b739e8f33359c6b 100755 (executable)
@@ -1,14 +1,49 @@
 #!/usr/bin/env python3
 import curses
-import socket
 import queue
 import threading
-from plomrogue.io_tcp import PlomSocket
 from plomrogue.game import GameBase
 from plomrogue.parser import Parser
 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex
 from plomrogue.things import ThingBase
 from plomrogue.misc import quote
+from plomrogue.errors import BrokenSocketConnection
+
+from ws4py.client import WebSocketBaseClient
+class WebSocketClient(WebSocketBaseClient):
+
+    def __init__(self, recv_handler, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.recv_handler = recv_handler
+        self.connect()
+
+    def received_message(self, message):
+        if message.is_text:
+            message = str(message)
+            self.recv_handler(message)
+
+    @property
+    def plom_closed(self):
+        return self.client_terminated
+
+from plomrogue.io_tcp import PlomSocket
+class PlomSocketClient(PlomSocket):
+
+    def __init__(self, recv_handler, url):
+        import socket
+        self.recv_handler = recv_handler
+        host, port = url.split(':')
+        super().__init__(socket.create_connection((host, port)))
+
+    def close(self):
+        self.socket.close()
+
+    def run(self):
+        try:
+            for msg in self.recv():
+                self.recv_handler(msg)
+        except BrokenSocketConnection:
+            pass  # we assume socket will be known as dead by now
 
 def cmd_TURN(game, n):
     game.turn = n
@@ -73,9 +108,7 @@ def cmd_GAME_STATE_COMPLETE(game):
         game.tui.query_info()
     player = game.get_thing(game.player_id, False)
     if player.position in game.portals:
-        #host, port = game.portals[player.position].split(':')
         game.tui.teleport_target_host = game.portals[player.position]
-        game.tui.teleport_target_port = 5000
         game.tui.switch_mode('teleport')
     game.turn_complete = True
     game.tui.do_refresh = True
@@ -152,11 +185,10 @@ class TUI:
             self.shows_info = shows_info
             self.is_intro = is_intro
 
-    def __init__(self, host, port):
+    def __init__(self, host):
         import os
         import json
         self.host = host
-        self.port = port
         self.mode_play = self.Mode('play')
         self.mode_study = self.Mode('study', shows_info=True)
         self.mode_edit = self.Mode('edit')
@@ -206,8 +238,10 @@ class TUI:
 
     def send(self, msg):
         try:
+            if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
+                raise BrokenSocketConnection
             self.socket.send(msg)
-        except BrokenPipeError:
+        except (BrokenPipeError, BrokenSocketConnection):
             self.log_msg('@ server disconnected :(')
             self.do_refresh = True
 
@@ -232,8 +266,7 @@ class TUI:
             else:
                 self.log_msg('@ enter username')
         elif self.mode.name == 'teleport':
-            self.log_msg("@ May teleport to %s:%s" % (self.teleport_target_host,
-                                                      self.teleport_target_port));
+            self.log_msg("@ May teleport to %s" % (self.teleport_target_host)),
             self.log_msg("@ Enter 'YES!' to enthusiastically affirm.");
         elif self.mode.name == 'annotate' and self.explorer in self.game.info_db:
             info = self.game.info_db[self.explorer]
@@ -262,6 +295,7 @@ class TUI:
         self.log_msg("  %s - switch to play mode" % self.keys['switch_to_play']);
 
     def loop(self, stdscr):
+        import time
 
         def safe_addstr(y, x, line):
             if y < self.size.y - 1 or x + len(line) < self.size.x:
@@ -275,19 +309,20 @@ class TUI:
                 stdscr.addstr(y, x, cut)
 
         def connect():
-            import time
 
-            def recv_loop():
-                for msg in self.socket.recv():
-                    if msg == 'BYE':
-                        break
+            def handle_recv(msg):
+                if msg == 'BYE':
+                    self.socket.close()
+                else:
                     self.queue.put(msg)
 
+            socket_client_class = PlomSocketClient
+            if self.host.startswith('ws://') or self.host.startswith('wss://'):
+                socket_client_class = WebSocketClient
             while True:
                 try:
-                    s = socket.create_connection((self.host, self.port))
-                    self.socket = PlomSocket(s)
-                    self.socket_thread = threading.Thread(target=recv_loop)
+                    self.socket = socket_client_class(handle_recv, self.host)
+                    self.socket_thread = threading.Thread(target=self.socket.run)
                     self.socket_thread.start()
                     self.switch_mode('login')
                     return
@@ -299,6 +334,8 @@ class TUI:
 
         def reconnect():
             self.send('QUIT')
+            time.sleep(0.1)  # FIXME necessitated by some some strange SSL race
+                             # conditions with ws4py, find out what exactly
             self.switch_mode('waiting_for_server')
             connect()
 
@@ -520,7 +557,6 @@ class TUI:
             elif self.mode == self.mode_teleport and key == '\n':
                 if self.input_ == 'YES!':
                     self.host = self.teleport_target_host
-                    self.port = self.teleport_target_port
                     reconnect()
                 else:
                     self.log_msg('@ teleport aborted')
@@ -548,8 +584,11 @@ class TUI:
                     self.send('TASK:FLATTEN_SURROUNDINGS')
                 elif key in self.movement_keys:
                     self.send('TASK:MOVE ' + self.movement_keys[key])
+                elif key == 'q':
+                    self.log_msg('quitting')
+                    self.send('QUIT')
             elif self.mode == self.mode_edit:
                 self.send('TASK:WRITE ' + key)
                 self.switch_mode('play')
 
-TUI('127.0.0.1', 5000)
+TUI('127.0.0.1:5000')