baseimage-tutorial-framework/tfw/components/frontend/message_queue_handler/message_queue_handler.py

91 lines
2.7 KiB
Python

import logging
from time import sleep, time
from queue import Queue, Empty
from threading import Thread
from contextlib import suppress
LOG = logging.getLogger(__name__)
class MessageQueueHandler:
keys = ['message.queue']
type_id = 'ControlEventHandler'
avg_word_len = 5
drain_poll_freq = 0.2
def __init__(self, wpm):
self.connector = None
self.wpm = wpm
self._queue = Queue()
self._drain_queue = Queue()
self._thread = Thread(target=self._dispatch_messages)
self._commands = {
'message.queue': self.handle_queue,
'message.queue.drain': self.handle_drain
}
def _dispatch_messages(self):
for message in iter(self._queue.get, None):
message['typing'] = not self._queue.empty()
self.connector.send_message(message)
self._sleep(self._get_sleep_time(message))
def _get_sleep_time(self, message):
words_per_min = message['wpm'] if 'wpm' in message else self.wpm
chars_per_min = self.avg_word_len * words_per_min / 60
return len(message['message']) / chars_per_min
def _sleep(self, seconds):
poll_freq = self.drain_poll_freq
if seconds < poll_freq:
poll_freq = seconds
sleep_until = time() + seconds
while time() < sleep_until:
sleep(poll_freq)
with suppress(Empty):
self._drain_queue.get(block=False)
self._drain()
break
def _drain(self):
with suppress(Empty):
while True:
message = self._queue.get(block=False)
if message is None:
break
message['typing'] = False
self.connector.send_message(message)
def handle_event(self, message, _):
try:
self._commands[message['key']](message)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
def handle_queue(self, message):
for unpacked in self._generate_messages_from_queue(message):
self._queue.put(unpacked)
@staticmethod
def _generate_messages_from_queue(queue_message):
for message in queue_message['messages']:
yield {
'key': 'message.send',
**message
}
def handle_drain(self, _):
self._drain_queue.put(True)
def start(self):
self._thread.start()
def cleanup(self):
# clearing the queue forces the loop in
# _dispatch_messages to block on _queue.get
self._queue.queue.clear()
self._queue.put(None)
self._thread.join()