dnd-music-console/src/croaker/server.py

119 lines
3.6 KiB
Python
Raw Normal View History

import logging
2024-03-05 22:15:51 -08:00
import os
import socket
import socketserver
import time
2024-03-01 01:00:17 -08:00
from croaker.gui import GUI
from croaker.path import playlist_root
2024-03-06 17:04:08 -08:00
from croaker.playlist import load_playlist
logger = logging.getLogger(__name__)
2024-03-05 22:21:56 -08:00
class RequestHandler(socketserver.StreamRequestHandler):
2024-03-05 23:25:21 -08:00
"""
Instantiated by the TCPServer when a request is received. Implements the
command and control protocol and issues commands to the GUI application.
2024-03-05 23:25:21 -08:00
"""
2024-03-26 00:51:16 -07:00
supported_commands = {
2024-03-06 17:04:08 -08:00
# command # help text
"PLAY": "PLAYLIST - Switch to the specified playlist.",
"LIST": "[PLAYLIST] - List playlists or contents of the specified list.",
"FFWD": " - Skip to the next track in the playlist.",
"HELP": " - Display command help.",
"KTHX": " - Close the current connection.",
"STOP": " - Stop the current track and stream silence.",
2024-03-26 00:51:16 -07:00
"STFU": " - Terminate the Croaker server.",
}
2024-03-17 14:44:36 -07:00
should_listen = True
def handle(self):
2024-03-05 23:25:21 -08:00
"""
Start a command and control session. Commands are read one line at a
time; the format is:
Byte Definition
-------------------
0-3 Command
4 Ignored
5+ Arguments
"""
while self.should_listen:
time.sleep(0.01)
self.data = self.rfile.readline().strip().decode()
logger.debug(f"Received: {self.data}")
try:
cmd = self.data[0:4].strip().upper()
if not cmd:
continue
elif cmd not in self.supported_commands:
self.send(f"ERR Unknown Command '{cmd}'")
except IndexError:
self.send(f"ERR Command not understood '{cmd}'")
2024-03-06 17:04:08 -08:00
continue
args = self.data[5:]
if cmd == "KTHX":
2024-03-05 22:15:51 -08:00
return self.send("KBAI")
handler = getattr(self, f"handle_{cmd}", None)
2024-03-06 17:04:08 -08:00
if not handler:
self.send(f"ERR No handler for {cmd}.")
continue
2024-03-06 17:04:08 -08:00
handler(args)
def send(self, msg):
2024-03-05 22:15:51 -08:00
return self.wfile.write(msg.encode() + b"\n")
2024-03-06 17:04:08 -08:00
def handle_PLAY(self, args):
self.server.player.load(args)
return self.send("OK")
def handle_BACK(self, args):
self.server.player.back_requested.set()
2024-03-06 17:04:08 -08:00
return self.send("OK")
def handle_FFWD(self, args):
self.server.player.ffwd_requested.set()
2024-03-05 22:15:51 -08:00
return self.send("OK")
2024-03-06 17:04:08 -08:00
def handle_LIST(self, args):
return self.send(self.server.list(args))
def handle_HELP(self, args):
2024-03-05 22:15:51 -08:00
return self.send("\n".join(f"{cmd} {txt}" for cmd, txt in self.supported_commands.items()))
def handle_STOP(self, args):
return self.server.player.stop_requested.set()
def handle_STFU(self, args):
self.send("Shutting down.")
self.server.shutdown()
2024-03-01 01:00:17 -08:00
class Controller(socketserver.TCPServer):
2024-03-05 23:25:21 -08:00
"""
A TCP Server that listens for commands and proxies the GUI audio player.
2024-03-05 23:25:21 -08:00
"""
2024-03-26 00:51:16 -07:00
def __init__(self, player: GUI):
self.player = player
super().__init__((os.environ["HOST"], int(os.environ["PORT"])), RequestHandler)
2024-03-01 01:00:17 -08:00
def server_bind(self):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
2024-03-01 01:00:17 -08:00
def shutdown(self):
self.player.shutdown_requested.set()
exit()
2024-03-06 17:04:08 -08:00
def list(self, playlist_name: str = None):
if playlist_name:
return str(load_playlist(playlist_name))
return "\n".join([str(p.name) for p in playlist_root().iterdir()])