Switch to sanic from flask.
This commit is contained in:
parent
ce000b2bbc
commit
fcf9d8dceb
10 changed files with 114 additions and 102 deletions
|
@ -1,7 +1,7 @@
|
|||
autopep8
|
||||
pygame==2.0.1
|
||||
flask
|
||||
flask-cors
|
||||
sanic
|
||||
sanic-cors
|
||||
mutagen
|
||||
sounddevice
|
||||
autopep8
|
||||
|
|
200
web_server.py
200
web_server.py
|
@ -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..."
|
||||
|
||||
|
@ -417,15 +419,25 @@ def WebServer(player_to: List[Queue], player_from: List[Queue], api_to: Queue, a
|
|||
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.logger.disabled = True
|
||||
|
||||
terminate = Terminator()
|
||||
while not terminate.terminate:
|
||||
try:
|
||||
app.run(
|
||||
host=server_state.state["host"],
|
||||
port=server_state.state["port"],
|
||||
debug=True,
|
||||
use_reloader=False,
|
||||
threaded=False # While API handles are singlethreaded.
|
||||
workers=1,
|
||||
auto_reload=False
|
||||
|
||||
# use_reloader=False,
|
||||
# threaded=False # While API handles are singlethreaded.
|
||||
)
|
||||
except Exception:
|
||||
break
|
||||
app.stop()
|
||||
|
|
Loading…
Reference in a new issue