- This change is very far reaching into the internals. For this reason a version bump is required.

- These is a very high chance of new bugs or repeats of old bugs. Be watchful!
- Removed periodic timeout for overlay ticking
- Removed 60hz timeout for reading websocket
- Removed 1hz timeout for text overlay
- Removed 1hz timeout for notification overlay
- Added a one-call timeout for each overlay which happens a configurable time after render to remove it excess data
- Changed flat bool needsrender to a function call set_needs_render
- Where needed, this schedules an idle callback to rerender, cutting down on multiple renders in extremely short time
- Ripped out do_read from connector
- Piped the websocket socket into GLib, allowing it to call back when new data is readable
- Implemented reconnect logic in GLib
- Shortened connect timeout as localhost should be rather quick
This commit is contained in:
Trigg 2024-02-13 23:24:48 +00:00
parent 667ad02a4c
commit 8cd376d311
7 changed files with 147 additions and 139 deletions

View file

@ -29,6 +29,11 @@ import calendar
import websocket
import requests
import gi
gi.require_version("Gtk", "3.0")
# pylint: disable=wrong-import-position,wrong-import-order
from gi.repository import GLib # nopep8
log = logging.getLogger(__name__)
@ -60,9 +65,13 @@ class DiscordConnector:
self.text_altered = False
self.text = []
self.authed = False
self.thread = None
self.last_rate_limit_send = 0
self.socket_watch = None
self.rate_limited_channels = []
self.reconnect_delay = 0
self.reconnect_cb = None
def get_access_token_stage1(self):
"""
@ -313,7 +322,6 @@ class DiscordConnector:
self.list_altered = True
self.userlist[j["data"]["user_id"]]["speaking"] = True
self.userlist[j["data"]["user_id"]]["lastspoken"] = time.time()
self.list_altered = True
self.set_in_room(j["data"]["user_id"], True)
elif j["evt"] == "SPEAKING_STOP":
self.list_altered = True
@ -465,14 +473,10 @@ class DiscordConnector:
Called when connection is closed
"""
log.warning("Connection closed")
self.discover.voice_overlay.set_blank()
if self.discover.text_overlay:
self.discover.text_overlay.set_blank()
if self.discover.notification_overlay:
self.discover.notification_overlay.set_blank()
self.websocket = None
self.reconnect_delay = 60 * 20 # Try again in 5 seconds ish
self.update_overlays_from_data
self.current_voice = "0"
self.schedule_reconnect()
def req_auth(self):
"""
@ -696,27 +700,14 @@ class DiscordConnector:
if self.websocket:
self.websocket.send(json.dumps(cmd))
def do_read(self):
"""
Poorly named logic center.
Checks for new data on socket, passes to on_message
Also passes out text data to text overlay and voice data to voice overlay
Called at 60Hz approximately but has near zero bearing on rendering
"""
# Ensure connection
if not self.websocket:
if self.reconnect_delay <= 0:
# No timeout left, connect to discord again
self.connect()
return True
else:
# Timeout requested, wait it out
self.reconnect_delay -= 1
return True
# Recreate a list of users in current room
def update_overlays_from_data(self):
if self.websocket == None:
self.discover.voice_overlay.set_blank()
if self.discover.text_overlay:
self.discover.text_overlay.set_blank()
if self.discover.notification_overlay:
self.discover.notification_overlay.set_blank()
return
newlist = []
for userid in self.in_room:
newlist.append(self.userlist[userid])
@ -731,32 +722,19 @@ class DiscordConnector:
self.text_altered = False
if self.authed and len(self.rate_limited_channels) > 0:
guild = self.rate_limited_channels.pop()
now = time.time()
if self.last_rate_limit_send < now - 60:
guild = self.rate_limited_channels.pop()
cmd = {
"cmd": "GET_CHANNELS",
"args": {
"guild_id": guild
},
"nonce": guild
}
self.websocket.send(json.dumps(cmd))
# Poll socket for new information
recv, _w, _e = select.select((self.websocket.sock,), (), (), 0)
while recv:
try:
# Receive & send to on_message
msg = self.websocket.recv()
self.on_message(msg)
if not self.websocket:
# Connection was closed in the meantime
return True
recv, _w, _e = select.select((self.websocket.sock,), (), (), 0)
except (websocket.WebSocketConnectionClosedException, json.decoder.JSONDecodeError):
self.on_close()
return True
return True
cmd = {
"cmd": "GET_CHANNELS",
"args": {
"guild_id": guild
},
"nonce": guild
}
self.websocket.send(json.dumps(cmd))
self.last_rate_limit_send = now
def start_listening_text(self, channel):
"""
@ -782,22 +760,56 @@ class DiscordConnector:
return
self.rate_limited_channels.append(guild_id)
def schedule_reconnect(self):
if self.reconnect_cb == None:
log.info("Scheduled a reconnect")
self.reconnect_cb = GLib.timeout_add_seconds(60, self.connect)
else:
log.error("Reconnect already scheduled")
def connect(self):
"""
Attempt to connect to websocket
Should not throw simply for being unable to connect, only for more serious issues
"""
log.info("Connecting...")
if self.websocket:
log.warn("Already connected?")
return
if self.reconnect_cb:
GLib.source_remove(self.reconnect_cb)
self.reconnect_cb = None
try:
self.websocket = websocket.create_connection(
"ws://127.0.0.1:6463/?v=1&client_id=%s" % (self.oauth_token),
origin="http://localhost:3000"
origin="http://localhost:3000",
timeout=0.1
)
self.warn_connection=True # Warn on next disconnect
if self.socket_watch:
GLib.source_remove(self.socket_watch)
self.socket_watch = GLib.io_add_watch(self.websocket.sock, GLib.IOCondition.HUP | GLib.IOCondition.IN | GLib.IOCondition.ERR, self.socket_glib)
except ConnectionError as error:
if self.warn_connection:
log.error(error)
self.warn_connection=False
self.reconnect_delay = 60 * 30 # Try again in a minute
self.schedule_reconnect()
def socket_glib(self, a=None, b=None):
if self.websocket:
recv, _w, _e = select.select((self.websocket.sock,), (), (), 0)
while recv:
try:
# Receive & send to on_message
msg = self.websocket.recv()
self.on_message(msg)
if not self.websocket:
# Connection was closed in the meantime
break
recv, _w, _e = select.select((self.websocket.sock,), (), (), 0)
except (websocket.WebSocketConnectionClosedException, json.decoder.JSONDecodeError):
self.on_close()
break
self.update_overlays_from_data()
return True