Switch to sanic from flask.

This commit is contained in:
Matthew Stratford 2021-04-18 02:03:44 +01:00
parent ce000b2bbc
commit fcf9d8dceb
10 changed files with 114 additions and 102 deletions

View file

@ -1,7 +1,7 @@
autopep8
pygame==2.0.1
flask
flask-cors
sanic
sanic-cors
mutagen
sounddevice
autopep8

View file

@ -1,5 +1,13 @@
from flask import Flask, render_template, send_from_directory, request, jsonify, abort
from flask_cors import CORS
from sanic import Sanic
from sanic.exceptions import NotFound, abort
from sanic.response import html, text, file, redirect
from sanic.response import json as resp_json
from sanic_cors import CORS
from jinja2 import Environment, FileSystemLoader
from urllib.parse import unquote
# , render_template, send_from_directory, request, jsonify, abort
#from flask_cors import CORS
from setproctitle import setproctitle
import logging
from typing import Any, Optional, List
@ -7,13 +15,22 @@ from multiprocessing.queues import Queue
from queue import Empty
from time import sleep
import json
import os
from helpers.os_environment import isBundelled, isMacOS
from helpers.logging_manager import LoggingManager
from helpers.device_manager import DeviceManager
from helpers.state_manager import StateManager
from helpers.the_terminator import Terminator
app = Flask(__name__, static_url_path="")
env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__)))
app = Sanic("BAPSicle Web Server")
def render_template(file, data, status=200):
template = env.get_template(file)
html_content = template.render(data=data)
return html(html_content, status=status)
logger: LoggingManager
@ -28,14 +45,14 @@ player_from_q: List[Queue] = []
# General UI Endpoints
@app.errorhandler(404)
def page_not_found(e: Any):
@app.exception(NotFound)
def page_not_found(request, e: Any):
data = {"ui_page": "404", "ui_title": "404"}
return render_template("404.html", data=data), 404
return render_template("404.html", data=data, status=404)
@app.route("/")
def ui_index():
def ui_index(request):
data = {
"ui_page": "index",
"ui_title": "",
@ -47,7 +64,7 @@ def ui_index():
@app.route("/status")
def ui_status():
def ui_status(request):
channel_states = []
for i in range(server_state.state["num_channels"]):
channel_states.append(status(i))
@ -58,7 +75,7 @@ def ui_status():
@app.route("/config/player")
def ui_config_player():
def ui_config_player(request):
channel_states = []
for i in range(server_state.state["num_channels"]):
channel_states.append(status(i))
@ -75,7 +92,7 @@ def ui_config_player():
@app.route("/config/server")
def ui_config_server():
def ui_config_server(request):
data = {
"ui_page": "server",
"ui_title": "Server Config",
@ -86,7 +103,7 @@ def ui_config_server():
@app.route("/config/server/update", methods=["POST"])
def ui_config_server_update():
def ui_config_server_update(request):
server_state.update("server_name", request.form["name"])
server_state.update("host", request.form["host"])
server_state.update("port", int(request.form["port"]))
@ -101,11 +118,11 @@ def ui_config_server_update():
server_state.update("myradio_base_url", request.form["myradio_base_url"])
server_state.update("myradio_api_url", request.form["myradio_api_url"])
# stopServer()
return ui_config_server()
return ui_config_server(request)
@app.route("/logs")
def ui_logs_list():
def ui_logs_list(request):
data = {
"ui_page": "logs",
"ui_title": "Logs",
@ -116,7 +133,7 @@ def ui_logs_list():
@app.route("/logs/<path:path>")
def ui_logs_render(path):
def ui_logs_render(request, path):
log_file = open("logs/{}.log".format(path))
data = {
"logs": log_file.read().splitlines(),
@ -131,99 +148,99 @@ def ui_logs_render(path):
# Just useful for messing arround without presenter / websockets.
@app.route("/player/<int:channel>/<command>")
def player_simple(channel: int, command: str):
@app.route("/player/<channel:int>/<command>")
def player_simple(request, channel: int, command: str):
simple_endpoints = ["play", "pause", "unpause", "stop", "unload", "clear"]
if command in simple_endpoints:
player_to_q[channel].put("UI:" + command.upper())
return ui_status()
return redirect("/status")
return page_not_found()
abort(404)
@app.route("/player/<int:channel>/seek/<float:pos>")
def player_seek(channel: int, pos: float):
@app.route("/player/<channel:int>/seek/<pos:float>")
def player_seek(request, channel: int, pos: float):
player_to_q[channel].put("UI:SEEK:" + str(pos))
return ui_status()
return redirect("/status")
@app.route("/player/<int:channel>/load/<int:channel_weight>")
def player_load(channel: int, channel_weight: int):
@app.route("/player/<channel:int>/load/<channel_weight:int>")
def player_load(request, channel: int, channel_weight: int):
player_to_q[channel].put("UI:LOAD:" + str(channel_weight))
return ui_status()
return redirect("/status")
@app.route("/player/<int:channel>/remove/<int:channel_weight>")
def player_remove(channel: int, channel_weight: int):
@app.route("/player/<channel:int>/remove/<channel_weight:int>")
def player_remove(request, channel: int, channel_weight: int):
player_to_q[channel].put("UI:REMOVE:" + str(channel_weight))
return ui_status()
return redirect("/status")
@app.route("/player/<int:channel>/output/<name>")
def player_output(channel: int, name: Optional[str]):
player_to_q[channel].put("UI:OUTPUT:" + str(name))
return ui_config_player()
@app.route("/player/<channel:int>/output/<name:string>")
def player_output(request, channel: int, name: Optional[str]):
player_to_q[channel].put("UI:OUTPUT:" + unquote(str(name)))
return redirect("/config/player")
@app.route("/player/<int:channel>/autoadvance/<int:state>")
def player_autoadvance(channel: int, state: int):
@app.route("/player/<channel:int>/autoadvance/<state:int>")
def player_autoadvance(request, channel: int, state: int):
player_to_q[channel].put("UI:AUTOADVANCE:" + str(state))
return ui_status()
return redirect("/status")
@app.route("/player/<int:channel>/repeat/<state>")
def player_repeat(channel: int, state: str):
@app.route("/player/<channel:int>/repeat/<state:string>")
def player_repeat(request, channel: int, state: str):
player_to_q[channel].put("UI:REPEAT:" + state.upper())
return ui_status()
return redirect("/status")
@app.route("/player/<int:channel>/playonload/<int:state>")
def player_playonload(channel: int, state: int):
@app.route("/player/<channel:int>/playonload/<state:int>")
def player_playonload(request, channel: int, state: int):
player_to_q[channel].put("UI:PLAYONLOAD:" + str(state))
return ui_status()
return redirect("/status")
@app.route("/player/<int:channel>/status")
def player_status_json(channel: int):
@app.route("/player/<channel:int>/status")
def player_status_json(request, channel: int):
return jsonify(status(channel))
return resp_json(status(channel))
@app.route("/player/all/stop")
def player_all_stop():
def player_all_stop(request):
for channel in player_to_q:
channel.put("UI:STOP")
return ui_status()
return redirect("/status")
# Show Plan Functions
@app.route("/plan/load/<int:timeslotid>")
def plan_load(timeslotid: int):
def plan_load(request, timeslotid: int):
for channel in player_to_q:
channel.put("UI:GET_PLAN:" + str(timeslotid))
return ui_status()
return redirect("/status")
@app.route("/plan/clear")
def plan_clear():
def plan_clear(request):
for channel in player_to_q:
channel.put("UI:CLEAR")
return ui_status()
return redirect("/status")
# API Proxy Endpoints
@app.route("/plan/list")
def api_list_showplans():
def api_list_showplans(request):
while not api_from_q.empty():
api_from_q.get() # Just waste any previous status responses.
@ -235,7 +252,7 @@ def api_list_showplans():
response = api_from_q.get_nowait()
if response.startswith("LIST_PLANS:"):
response = response[response.index(":") + 1:]
return response
return text(response)
except Empty:
pass
@ -244,7 +261,7 @@ def api_list_showplans():
@app.route("/library/search/<type>")
def api_search_library(type: str):
def api_search_library(request, type: str):
if type not in ["managed", "track"]:
abort(404)
@ -253,17 +270,17 @@ def api_search_library(type: str):
api_from_q.get() # Just waste any previous status responses.
params = json.dumps(
{"title": request.args.get(
"title"), "artist": request.args.get("artist")}
{"title": request.args.get("title"), "artist": request.args.get("artist")}
)
api_to_q.put("SEARCH_TRACK:{}".format(params))
command = "SEARCH_TRACK:{}".format(params)
api_to_q.put(command)
while True:
try:
response = api_from_q.get_nowait()
if response.startswith("SEARCH_TRACK:"):
response = response.split(":", 1)[1]
return response
response = response[len(command)+1:]
return text(response)
except Empty:
pass
@ -271,8 +288,8 @@ def api_search_library(type: str):
sleep(0.02)
@app.route("/library/playlists/<type>")
def api_get_playlists(type: str):
@app.route("/library/playlists/<type:string>")
def api_get_playlists(request, type: str):
if type not in ["music", "aux"]:
abort(401)
@ -288,7 +305,7 @@ def api_get_playlists(type: str):
response = api_from_q.get_nowait()
if response.startswith(command):
response = response.split(":", 1)[1]
return response
return text(response)
except Empty:
pass
@ -296,8 +313,8 @@ def api_get_playlists(type: str):
sleep(0.02)
@app.route("/library/playlist/<type>/<library_id>")
def api_get_playlist(type: str, library_id: str):
@app.route("/library/playlist/<type:string>/<library_id:string>")
def api_get_playlist(request, type: str, library_id: str):
if type not in ["music", "aux"]:
abort(401)
@ -315,7 +332,7 @@ def api_get_playlist(type: str, library_id: str):
response = response[len(command) + 1:]
if response == "null":
abort(401)
return response
return text(response)
except Empty:
pass
@ -327,43 +344,28 @@ def api_get_playlist(type: str, library_id: str):
@app.route("/status-json")
def json_status():
def json_status(request):
channel_states = []
for i in range(server_state.state["num_channels"]):
channel_states.append(status(i))
return {"server": server_state.state, "channels": channel_states}
return resp_json({"server": server_state.state, "channels": channel_states})
# Get audio for UI to generate waveforms.
@app.route("/audiofile/<type>/<int:id>")
def audio_file(type: str, id: int):
@app.route("/audiofile/<type:string>/<id:int>")
async def audio_file(request, type: str, id: int):
if type not in ["managed", "track"]:
abort(404)
return send_from_directory("music-tmp", type + "-" + str(id) + ".mp3")
return await file("music-tmp/" + type + "-" + str(id) + ".mp3")
# Static Files
@app.route("/favicon.ico")
def serve_favicon():
return send_from_directory("ui-static", "favicon.ico")
@app.route("/static/<path:path>")
def serve_static(path: str):
return send_from_directory("ui-static", path)
@app.route("/presenter/")
def serve_presenter_index():
return send_from_directory("presenter-build", "index.html")
@app.route("/presenter/<path:path>")
def serve_presenter_static(path: str):
return send_from_directory("presenter-build", path)
app.static("/favicon.ico", "./ui-static/favicon.ico", name="ui-favicon")
app.static("/static", "./ui-static", name="ui-static")
app.static("/presenter/", "./presenter-build/index.html", strict_slashes=True)
app.static("/presenter", "./presenter-build")
# Helper Functions
@ -398,7 +400,7 @@ def status(channel: int):
@app.route("/quit")
def quit():
def quit(request):
# stopServer()
return "Shutting down..."
@ -413,19 +415,29 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], api_to: Queue, a
api_to_q = api_to
server_state = state
process_title = "WebServer"
process_title = "Web Server"
setproctitle(process_title)
CORS(app, supports_credentials=True) # Allow ALL CORS!!!
if not isBundelled():
log = logging.getLogger("werkzeug")
log.disabled = True
# if not isBundelled():
# log = logging.getLogger("werkzeug")
# log.disabled = True
app.logger.disabled = True
app.run(
host=server_state.state["host"],
port=server_state.state["port"],
debug=True,
use_reloader=False,
threaded=False # While API handles are singlethreaded.
)
#app.logger.disabled = True
terminate = Terminator()
while not terminate.terminate:
try:
app.run(
host=server_state.state["host"],
port=server_state.state["port"],
debug=True,
workers=1,
auto_reload=False
# use_reloader=False,
# threaded=False # While API handles are singlethreaded.
)
except Exception:
break
app.stop()