119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
import logging
|
|
import os
|
|
import socket
|
|
import socketserver
|
|
import time
|
|
|
|
from croaker.gui import GUI
|
|
from croaker.path import playlist_root
|
|
from croaker.playlist import load_playlist
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RequestHandler(socketserver.StreamRequestHandler):
|
|
"""
|
|
Instantiated by the TCPServer when a request is received. Implements the
|
|
command and control protocol and issues commands to the GUI application.
|
|
"""
|
|
|
|
supported_commands = {
|
|
# 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.",
|
|
"STFU": " - Terminate the Croaker server.",
|
|
}
|
|
|
|
should_listen = True
|
|
|
|
def handle(self):
|
|
"""
|
|
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}'")
|
|
continue
|
|
|
|
args = self.data[5:]
|
|
if cmd == "KTHX":
|
|
return self.send("KBAI")
|
|
|
|
handler = getattr(self, f"handle_{cmd}", None)
|
|
if not handler:
|
|
self.send(f"ERR No handler for {cmd}.")
|
|
continue
|
|
|
|
handler(args)
|
|
|
|
def send(self, msg):
|
|
return self.wfile.write(msg.encode() + b"\n")
|
|
|
|
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()
|
|
return self.send("OK")
|
|
|
|
def handle_FFWD(self, args):
|
|
self.server.player.ffwd_requested.set()
|
|
return self.send("OK")
|
|
|
|
def handle_LIST(self, args):
|
|
return self.send(self.server.list(args))
|
|
|
|
def handle_HELP(self, args):
|
|
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()
|
|
|
|
|
|
class Controller(socketserver.TCPServer):
|
|
"""
|
|
A TCP Server that listens for commands and proxies the GUI audio player.
|
|
"""
|
|
|
|
def __init__(self, player: GUI):
|
|
self.player = player
|
|
super().__init__((os.environ["HOST"], int(os.environ["PORT"])), RequestHandler)
|
|
|
|
def server_bind(self):
|
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
self.socket.bind(self.server_address)
|
|
|
|
def shutdown(self):
|
|
self.player.shutdown_requested.set()
|
|
exit()
|
|
|
|
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()])
|