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):
 class PlomSocket:
 
     def __init__(self, socket):
@@ -32,7 +33,6 @@ class PlomSocket:
         <http://stackoverflow.com/q/34919846>
 
         """
         <http://stackoverflow.com/q/34919846>
 
         """
-        from plomrogue.errors import BrokenSocketConnection
         escaped_message = ''
         for char in message:
             if char in ('\\', '$'):
         escaped_message = ''
         for char in message:
             if char in ('\\', '$'):
@@ -77,7 +77,11 @@ class PlomSocket:
         data = b''
         msg = b''
         while True:
         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:
             if 0 == len(data):
                 break
             for c in data:
index 4cf00d1f6f4f1a97c21ac1f200d51e4848d92b5f..7d7d093cc3be48c2d5ab27a81e9a634c703d2f22 100644 (file)
@@ -1 +1,2 @@
 SimpleWebSocketServer
 SimpleWebSocketServer
+ws4py
index bb3dac263e84670c8f9ef851f8b87844d6b9e4a6..689ecc1b1686f023799ae7f94b739e8f33359c6b 100755 (executable)
@@ -1,14 +1,49 @@
 #!/usr/bin/env python3
 import curses
 #!/usr/bin/env python3
 import curses
-import socket
 import queue
 import threading
 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.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
 
 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:
         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_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
         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
 
             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
         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')
         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:
 
     def send(self, msg):
         try:
+            if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
+                raise BrokenSocketConnection
             self.socket.send(msg)
             self.socket.send(msg)
-        except BrokenPipeError:
+        except (BrokenPipeError, BrokenSocketConnection):
             self.log_msg('@ server disconnected :(')
             self.do_refresh = True
 
             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':
             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]
             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):
         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:
 
         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():
                 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)
 
                     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:
             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
                     self.socket_thread.start()
                     self.switch_mode('login')
                     return
@@ -299,6 +334,8 @@ class TUI:
 
         def reconnect():
             self.send('QUIT')
 
         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()
 
             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
             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')
                     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])
                     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')
 
             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')