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 asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import socket
|
||||||
from typing import Any, Awaitable, Callable, Dict, Optional
|
from typing import Any, Awaitable, Callable, Dict, Optional
|
||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
@ -73,9 +75,14 @@ class Connection:
|
||||||
"on_part-event" and "on_ping".
|
"on_part-event" and "on_ping".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Maximum duration between euphoria's ping messages
|
# Maximum duration between euphoria's ping messages. Euphoria usually sends
|
||||||
PING_TIMEOUT = 60 # seconds
|
# 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"
|
_NOT_RUNNING = "not running"
|
||||||
_CONNECTING = "connecting"
|
_CONNECTING = "connecting"
|
||||||
_RUNNING = "running"
|
_RUNNING = "running"
|
||||||
|
|
@ -111,6 +118,12 @@ class Connection:
|
||||||
event: str,
|
event: str,
|
||||||
callback: Callable[..., Awaitable[None]]
|
callback: Callable[..., Awaitable[None]]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Register an event callback.
|
||||||
|
|
||||||
|
For an overview of the possible events, see the Connection docstring.
|
||||||
|
"""
|
||||||
|
|
||||||
self._events.register(event, callback)
|
self._events.register(event, callback)
|
||||||
|
|
||||||
# Connecting and disconnecting
|
# Connecting and disconnecting
|
||||||
|
|
@ -171,8 +184,8 @@ class Connection:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# TODO list all of the ways that creating a connection can go wrong
|
except (websockets.InvalidHandshake, websockets.InvalidStatusCode,
|
||||||
except websockets.InvalidStatusCode:
|
socket.gaierror):
|
||||||
logger.debug("Connection failed")
|
logger.debug("Connection failed")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -194,7 +207,7 @@ class Connection:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._state != self._RUNNING:
|
if self._state != self._RUNNING:
|
||||||
raise IncorrectStateException()
|
raise IncorrectStateException("This should never happen")
|
||||||
|
|
||||||
logger.debug("Reconnecting...")
|
logger.debug("Reconnecting...")
|
||||||
self._events.fire("reconnecting")
|
self._events.fire("reconnecting")
|
||||||
|
|
@ -214,6 +227,20 @@ class Connection:
|
||||||
return success
|
return success
|
||||||
|
|
||||||
async def connect(self) -> bool:
|
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.
|
# Special exception message for _CONNECTING.
|
||||||
if self._state == self._CONNECTING:
|
if self._state == self._CONNECTING:
|
||||||
raise IncorrectStateException(("connect() may not be called"
|
raise IncorrectStateException(("connect() may not be called"
|
||||||
|
|
@ -248,10 +275,12 @@ class Connection:
|
||||||
return success
|
return success
|
||||||
|
|
||||||
async def disconnect(self) -> None:
|
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,
|
Close and stop the Connection, if it is currently (re-)connecting or
|
||||||
# making for a total of 67 lines. Its comments to code ratio is about
|
running. Does nothing if the Connection is not running.
|
||||||
# 1.316.
|
|
||||||
|
This function returns once the Connection has stopped running.
|
||||||
|
"""
|
||||||
|
|
||||||
# Possible states left: _NOT_RUNNING, _CONNECTING, _RUNNING,
|
# Possible states left: _NOT_RUNNING, _CONNECTING, _RUNNING,
|
||||||
# _RECONNECTING, _DISCONNECTING
|
# _RECONNECTING, _DISCONNECTING
|
||||||
|
|
@ -318,6 +347,38 @@ class Connection:
|
||||||
|
|
||||||
logger.debug("Disconnected")
|
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
|
# Running
|
||||||
|
|
||||||
async def _run(self) -> None:
|
async def _run(self) -> None:
|
||||||
|
|
@ -326,6 +387,14 @@ class Connection:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while True:
|
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:
|
if self._state != self._RUNNING:
|
||||||
logger.debug("Exiting event loop")
|
logger.debug("Exiting event loop")
|
||||||
return
|
return
|
||||||
|
|
@ -334,9 +403,9 @@ class Connection:
|
||||||
try:
|
try:
|
||||||
logger.debug("Receiving ws packets")
|
logger.debug("Receiving ws packets")
|
||||||
async for packet in self._ws:
|
async for packet in self._ws:
|
||||||
self._process_packet(packet)
|
packet_data = json.loads(packet)
|
||||||
except Exception as e: # TODO use proper exceptions
|
self._process_packet(packet_data)
|
||||||
print(e)
|
except websockets.ConnectionClosed:
|
||||||
logger.debug("Stopped receiving ws packets")
|
logger.debug("Stopped receiving ws packets")
|
||||||
else:
|
else:
|
||||||
logger.debug("No ws connection found")
|
logger.debug("No ws connection found")
|
||||||
|
|
@ -354,10 +423,32 @@ class Connection:
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug(f"Sleeping for {self.RECONNECT_DELAY}s and retrying")
|
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):
|
def _process_packet(self, packet: Any) -> None:
|
||||||
print(str(packet)[:50])
|
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
|
pass # TODO
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue