Document stuff
Also, use the correct exceptions when interacting with _ws
This commit is contained in:
parent
c60526a34d
commit
2de2cbf92c
1 changed files with 107 additions and 16 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import socket
|
||||
from typing import Any, Awaitable, Callable, Dict, Optional
|
||||
|
||||
import websockets
|
||||
|
|
@ -73,9 +75,14 @@ class Connection:
|
|||
"on_part-event" and "on_ping".
|
||||
"""
|
||||
|
||||
# Maximum duration between euphoria's ping messages
|
||||
PING_TIMEOUT = 60 # seconds
|
||||
# Maximum duration between euphoria's ping messages. Euphoria usually sends
|
||||
# ping messages every 20 to 30 seconds.
|
||||
PING_TIMEOUT = 40 # seconds
|
||||
|
||||
# The delay between reconnect attempts.
|
||||
RECONNECT_DELAY = 40 # seconds
|
||||
|
||||
# States the Connection may be in
|
||||
_NOT_RUNNING = "not running"
|
||||
_CONNECTING = "connecting"
|
||||
_RUNNING = "running"
|
||||
|
|
@ -111,6 +118,12 @@ class Connection:
|
|||
event: str,
|
||||
callback: Callable[..., Awaitable[None]]
|
||||
) -> None:
|
||||
"""
|
||||
Register an event callback.
|
||||
|
||||
For an overview of the possible events, see the Connection docstring.
|
||||
"""
|
||||
|
||||
self._events.register(event, callback)
|
||||
|
||||
# Connecting and disconnecting
|
||||
|
|
@ -171,8 +184,8 @@ class Connection:
|
|||
|
||||
return True
|
||||
|
||||
# TODO list all of the ways that creating a connection can go wrong
|
||||
except websockets.InvalidStatusCode:
|
||||
except (websockets.InvalidHandshake, websockets.InvalidStatusCode,
|
||||
socket.gaierror):
|
||||
logger.debug("Connection failed")
|
||||
return False
|
||||
|
||||
|
|
@ -194,7 +207,7 @@ class Connection:
|
|||
"""
|
||||
|
||||
if self._state != self._RUNNING:
|
||||
raise IncorrectStateException()
|
||||
raise IncorrectStateException("This should never happen")
|
||||
|
||||
logger.debug("Reconnecting...")
|
||||
self._events.fire("reconnecting")
|
||||
|
|
@ -214,6 +227,20 @@ class Connection:
|
|||
return success
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""
|
||||
Attempt to create a connection to the Connection's url.
|
||||
|
||||
Returns True if the Connection could connect to the url and is now
|
||||
running. Returns False if the Connection could not connect to the url
|
||||
and is not running.
|
||||
|
||||
Exceptions:
|
||||
|
||||
This function must be called while the connection is not running,
|
||||
otherwise an IncorrectStateException will be thrown. To stop a
|
||||
Connection, use disconnect().
|
||||
"""
|
||||
|
||||
# Special exception message for _CONNECTING.
|
||||
if self._state == self._CONNECTING:
|
||||
raise IncorrectStateException(("connect() may not be called"
|
||||
|
|
@ -248,10 +275,12 @@ class Connection:
|
|||
return success
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
# Fun fact: This function consists of 25 lines of comments, 19 lines of
|
||||
# code, 16 lines of whitespace and 7 lines of logging statements,
|
||||
# making for a total of 67 lines. Its comments to code ratio is about
|
||||
# 1.316.
|
||||
"""
|
||||
Close and stop the Connection, if it is currently (re-)connecting or
|
||||
running. Does nothing if the Connection is not running.
|
||||
|
||||
This function returns once the Connection has stopped running.
|
||||
"""
|
||||
|
||||
# Possible states left: _NOT_RUNNING, _CONNECTING, _RUNNING,
|
||||
# _RECONNECTING, _DISCONNECTING
|
||||
|
|
@ -318,6 +347,38 @@ class Connection:
|
|||
|
||||
logger.debug("Disconnected")
|
||||
|
||||
async def reconnect(self) -> None:
|
||||
"""
|
||||
Forces the Connection to reconnect.
|
||||
|
||||
This function may return before the reconnect process is finished.
|
||||
|
||||
Exceptions:
|
||||
|
||||
This function must be called while the connection is (re-)connecting or
|
||||
running, otherwise an IncorrectStateException will be thrown.
|
||||
"""
|
||||
|
||||
if self._state in [self._CONNECTING, self._RECONNECTING]:
|
||||
logger.debug("Already (re-)connecting, waiting for it to finish...")
|
||||
async with self._connected_condition:
|
||||
await self._connected_condition.wait()
|
||||
|
||||
logger.debug("(Re-)connected, finished waiting")
|
||||
return
|
||||
|
||||
if self._state != self._RUNNING:
|
||||
raise IncorrectStateException(("reconnect() may not be called while"
|
||||
" the connection is not running."))
|
||||
|
||||
# Disconnecting via task because otherwise, the _connected_condition
|
||||
# might fire before we start waiting for it.
|
||||
#
|
||||
# The event loop will reconenct after the ws connection has been
|
||||
# disconnected.
|
||||
logger.debug("Disconnecting and letting the event loop reconnect")
|
||||
await self._disconnect()
|
||||
|
||||
# Running
|
||||
|
||||
async def _run(self) -> None:
|
||||
|
|
@ -326,6 +387,14 @@ class Connection:
|
|||
"""
|
||||
|
||||
while True:
|
||||
# The "Exiting event loop" checks are a bit ugly. They're in place
|
||||
# so that the event loop exits on its own at predefined positions
|
||||
# instead of randomly getting thrown a CancelledError.
|
||||
#
|
||||
# Now that I think about it, the whole function looks kinda ugly.
|
||||
# Maybe one day (yeah, right), I'll clean this up. I want to get it
|
||||
# working first though.
|
||||
|
||||
if self._state != self._RUNNING:
|
||||
logger.debug("Exiting event loop")
|
||||
return
|
||||
|
|
@ -334,9 +403,9 @@ class Connection:
|
|||
try:
|
||||
logger.debug("Receiving ws packets")
|
||||
async for packet in self._ws:
|
||||
self._process_packet(packet)
|
||||
except Exception as e: # TODO use proper exceptions
|
||||
print(e)
|
||||
packet_data = json.loads(packet)
|
||||
self._process_packet(packet_data)
|
||||
except websockets.ConnectionClosed:
|
||||
logger.debug("Stopped receiving ws packets")
|
||||
else:
|
||||
logger.debug("No ws connection found")
|
||||
|
|
@ -354,10 +423,32 @@ class Connection:
|
|||
return
|
||||
|
||||
logger.debug(f"Sleeping for {self.RECONNECT_DELAY}s and retrying")
|
||||
await asyncio.sleep(self._RECONNECT_DELAY)
|
||||
await asyncio.sleep(self.RECONNECT_DELAY)
|
||||
|
||||
def _process_packet(self, packet):
|
||||
print(str(packet)[:50])
|
||||
def _process_packet(self, packet: Any) -> None:
|
||||
print(str(packet)[:100]) # TODO implement
|
||||
|
||||
async def send(self, packet: Any) -> Any:
|
||||
async def send(self,
|
||||
packet_type: str,
|
||||
data: Any,
|
||||
await_reply: bool = True
|
||||
) -> Optional[Any]:
|
||||
"""
|
||||
Send a packet of type packet_type to the server.
|
||||
|
||||
The object passed as data will make up the packet's "data" section and
|
||||
must be json-serializable.
|
||||
|
||||
This function will return the complete json-deserialized reply package,
|
||||
unless await_reply is set to False, in which case it will immediately
|
||||
return None.
|
||||
|
||||
Exceptions:
|
||||
|
||||
This function must be called while the Connection is (re-)connecting or
|
||||
running, otherwise an IncorrectStateException will be thrown.
|
||||
|
||||
If the connection closes unexpectedly while sending the packet or
|
||||
waiting for the reply, a ConnectionClosedException will be thrown.
|
||||
"""
|
||||
pass # TODO
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue