2021-08-16 22:43:09 +00:00
|
|
|
from helpers.normalisation import get_normalised_filename_if_available
|
2021-04-18 02:14:14 +00:00
|
|
|
from helpers.myradio_api import MyRadioAPI
|
2021-08-17 22:54:24 +00:00
|
|
|
from sanic import Sanic, log
|
2021-04-18 01:03:44 +00:00
|
|
|
from sanic.exceptions import NotFound, abort
|
2021-08-17 22:54:24 +00:00
|
|
|
from sanic.response import html, file, redirect
|
2021-04-18 01:03:44 +00:00
|
|
|
from sanic.response import json as resp_json
|
|
|
|
from sanic_cors import CORS
|
2021-04-18 02:52:34 +00:00
|
|
|
from syncer import sync
|
2021-04-18 20:32:31 +00:00
|
|
|
import asyncio
|
2021-04-18 01:03:44 +00:00
|
|
|
|
|
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
from urllib.parse import unquote
|
2021-04-17 20:28:57 +00:00
|
|
|
from setproctitle import setproctitle
|
|
|
|
from typing import Any, Optional, List
|
|
|
|
from multiprocessing.queues import Queue
|
|
|
|
from queue import Empty
|
|
|
|
from time import sleep
|
|
|
|
import json
|
2021-04-18 01:03:44 +00:00
|
|
|
import os
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-05-25 22:10:39 +00:00
|
|
|
from helpers.os_environment import isBundelled, resolve_external_file_path, resolve_local_file_path
|
2021-04-17 20:28:57 +00:00
|
|
|
from helpers.logging_manager import LoggingManager
|
|
|
|
from helpers.device_manager import DeviceManager
|
|
|
|
from helpers.state_manager import StateManager
|
2021-04-18 01:03:44 +00:00
|
|
|
from helpers.the_terminator import Terminator
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
env = Environment(loader=FileSystemLoader('%s/ui-templates/' % os.path.dirname(__file__)))
|
2021-08-17 22:54:24 +00:00
|
|
|
|
|
|
|
# From Sanic's default, but set to log to file.
|
|
|
|
LOGGING_CONFIG = dict(
|
|
|
|
version=1,
|
|
|
|
disable_existing_loggers=False,
|
|
|
|
loggers={
|
|
|
|
"sanic.root": {"level": "INFO", "handlers": ["file"]},
|
|
|
|
"sanic.error": {
|
|
|
|
"level": "INFO",
|
|
|
|
"handlers": ["error_file"],
|
|
|
|
"propagate": True,
|
|
|
|
"qualname": "sanic.error",
|
|
|
|
},
|
|
|
|
"sanic.access": {
|
|
|
|
"level": "INFO",
|
|
|
|
"handlers": ["access_file"],
|
|
|
|
"propagate": True,
|
|
|
|
"qualname": "sanic.access",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
handlers={
|
|
|
|
"file": {
|
|
|
|
"class": "logging.FileHandler",
|
|
|
|
"formatter": "generic",
|
|
|
|
"filename": "logs/WebServer.log"
|
|
|
|
},
|
|
|
|
"error_file": {
|
|
|
|
"class": "logging.FileHandler",
|
|
|
|
"formatter": "generic",
|
|
|
|
"filename": "logs/WebServer.log"
|
|
|
|
},
|
|
|
|
"access_file": {
|
|
|
|
"class": "logging.FileHandler",
|
|
|
|
"formatter": "access",
|
|
|
|
"filename": "logs/WebServer.log"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
formatters={
|
|
|
|
"generic": {
|
|
|
|
"format": "%(asctime)s | [%(process)d] [%(levelname)s] %(message)s",
|
|
|
|
"class": "logging.Formatter",
|
|
|
|
},
|
|
|
|
"access": {
|
|
|
|
"format": "%(asctime)s | (%(name)s)[%(levelname)s][%(host)s]: "
|
|
|
|
+ "%(request)s %(message)s %(status)d %(byte)d",
|
|
|
|
"class": "logging.Formatter",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
app = Sanic("BAPSicle Web Server", log_config=LOGGING_CONFIG)
|
2021-04-18 01:03:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
def render_template(file, data, status=200):
|
|
|
|
template = env.get_template(file)
|
|
|
|
html_content = template.render(data=data)
|
|
|
|
return html(html_content, status=status)
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
logger: LoggingManager
|
|
|
|
server_state: StateManager
|
2021-04-18 02:14:14 +00:00
|
|
|
api: MyRadioAPI
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
player_to_q: List[Queue] = []
|
|
|
|
player_from_q: List[Queue] = []
|
|
|
|
|
|
|
|
# General UI Endpoints
|
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.exception(NotFound)
|
|
|
|
def page_not_found(request, e: Any):
|
2021-04-17 20:28:57 +00:00
|
|
|
data = {"ui_page": "404", "ui_title": "404"}
|
2021-04-18 01:03:44 +00:00
|
|
|
return render_template("404.html", data=data, status=404)
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/")
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_index(request):
|
2021-04-27 20:48:17 +00:00
|
|
|
config = server_state.get()
|
2021-04-17 20:28:57 +00:00
|
|
|
data = {
|
|
|
|
"ui_page": "index",
|
|
|
|
"ui_title": "",
|
2021-04-27 20:48:17 +00:00
|
|
|
"server_version": config["server_version"],
|
|
|
|
"server_build": config["server_build"],
|
|
|
|
"server_name": config["server_name"],
|
|
|
|
"server_beta": config["server_beta"],
|
|
|
|
"server_branch": config["server_branch"]
|
2021-04-17 20:28:57 +00:00
|
|
|
}
|
|
|
|
return render_template("index.html", data=data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/status")
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_status(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
channel_states = []
|
2021-04-18 19:27:54 +00:00
|
|
|
for i in range(server_state.get()["num_channels"]):
|
2021-04-17 20:28:57 +00:00
|
|
|
channel_states.append(status(i))
|
|
|
|
|
|
|
|
data = {"channels": channel_states,
|
|
|
|
"ui_page": "status", "ui_title": "Status"}
|
|
|
|
return render_template("status.html", data=data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/config/player")
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_config_player(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
channel_states = []
|
2021-04-18 19:27:54 +00:00
|
|
|
for i in range(server_state.get()["num_channels"]):
|
2021-04-17 20:28:57 +00:00
|
|
|
channel_states.append(status(i))
|
|
|
|
|
|
|
|
outputs = DeviceManager.getAudioOutputs()
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"channels": channel_states,
|
|
|
|
"outputs": outputs,
|
|
|
|
"ui_page": "config",
|
|
|
|
"ui_title": "Player Config",
|
|
|
|
}
|
|
|
|
return render_template("config_player.html", data=data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/config/server")
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_config_server(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
data = {
|
|
|
|
"ui_page": "server",
|
|
|
|
"ui_title": "Server Config",
|
2021-04-18 19:27:54 +00:00
|
|
|
"state": server_state.get(),
|
2021-04-17 20:28:57 +00:00
|
|
|
"ser_ports": DeviceManager.getSerialPorts(),
|
2021-06-20 23:22:29 +00:00
|
|
|
"tracklist_modes": ["off", "on", "delayed", "fader-live"]
|
2021-04-17 20:28:57 +00:00
|
|
|
}
|
|
|
|
return render_template("config_server.html", data=data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/config/server/update", methods=["POST"])
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_config_server_update(request):
|
2021-04-22 22:14:55 +00:00
|
|
|
# TODO Validation!
|
|
|
|
|
2021-04-18 20:18:20 +00:00
|
|
|
server_state.update("server_name", request.form.get("name"))
|
|
|
|
server_state.update("host", request.form.get("host"))
|
|
|
|
server_state.update("port", int(request.form.get("port")))
|
|
|
|
server_state.update("num_channels", int(request.form.get("channels")))
|
|
|
|
server_state.update("ws_port", int(request.form.get("ws_port")))
|
|
|
|
server_state.update("serial_port", request.form.get("serial_port"))
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
# Because we're not showing the api key once it's set.
|
2021-04-18 20:18:20 +00:00
|
|
|
if "myradio_api_key" in request.form and request.form.get("myradio_api_key") != "":
|
|
|
|
server_state.update("myradio_api_key", request.form.get("myradio_api_key"))
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 20:18:20 +00:00
|
|
|
server_state.update("myradio_base_url", request.form.get("myradio_base_url"))
|
|
|
|
server_state.update("myradio_api_url", request.form.get("myradio_api_url"))
|
2021-04-22 22:14:55 +00:00
|
|
|
server_state.update("myradio_api_tracklist_source", request.form.get("myradio_api_tracklist_source"))
|
|
|
|
server_state.update("tracklist_mode", request.form.get("tracklist_mode"))
|
2021-04-18 20:18:20 +00:00
|
|
|
|
|
|
|
return redirect("/restart")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/logs")
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_logs_list(request):
|
2021-05-25 22:10:39 +00:00
|
|
|
files = os.listdir(resolve_external_file_path("/logs"))
|
|
|
|
log_files = []
|
|
|
|
for file in files:
|
|
|
|
if file.endswith(".log"):
|
|
|
|
log_files.append(file.rstrip(".log"))
|
|
|
|
|
|
|
|
log_files.sort()
|
2021-04-17 20:28:57 +00:00
|
|
|
data = {
|
|
|
|
"ui_page": "logs",
|
|
|
|
"ui_title": "Logs",
|
2021-05-25 22:10:39 +00:00
|
|
|
"logs": log_files
|
2021-04-17 20:28:57 +00:00
|
|
|
}
|
|
|
|
return render_template("loglist.html", data=data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/logs/<path:path>")
|
2021-04-18 01:03:44 +00:00
|
|
|
def ui_logs_render(request, path):
|
2021-05-15 22:51:12 +00:00
|
|
|
page = request.args.get("page")
|
|
|
|
if not page:
|
|
|
|
return redirect(f"/logs/{path}?page=1")
|
|
|
|
page = int(page)
|
|
|
|
assert page >= 1
|
|
|
|
|
2021-05-25 22:10:39 +00:00
|
|
|
log_file = open(resolve_external_file_path("/logs/{}.log").format(path))
|
2021-04-17 20:28:57 +00:00
|
|
|
data = {
|
2021-05-15 22:51:12 +00:00
|
|
|
"logs": log_file.read().splitlines()[-300*page:(-300*(page-1) if page > 1 else None)][::-1],
|
2021-04-17 20:28:57 +00:00
|
|
|
"ui_page": "logs",
|
|
|
|
"ui_title": "Logs - {}".format(path),
|
2021-05-15 22:51:12 +00:00
|
|
|
"page": page
|
2021-04-17 20:28:57 +00:00
|
|
|
}
|
|
|
|
log_file.close()
|
|
|
|
return render_template("log.html", data=data)
|
|
|
|
|
|
|
|
|
|
|
|
# Player Audio Control Endpoints
|
|
|
|
# Just useful for messing arround without presenter / websockets.
|
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/<command>")
|
|
|
|
def player_simple(request, channel: int, command: str):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
simple_endpoints = ["play", "pause", "unpause", "stop", "unload", "clear"]
|
|
|
|
if command in simple_endpoints:
|
|
|
|
player_to_q[channel].put("UI:" + command.upper())
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
abort(404)
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
@app.route("/player/<channel:int>/seek/<pos:number>")
|
2021-04-18 01:03:44 +00:00
|
|
|
def player_seek(request, channel: int, pos: float):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
player_to_q[channel].put("UI:SEEK:" + str(pos))
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/load/<channel_weight:int>")
|
|
|
|
def player_load(request, channel: int, channel_weight: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
player_to_q[channel].put("UI:LOAD:" + str(channel_weight))
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/remove/<channel_weight:int>")
|
|
|
|
def player_remove(request, channel: int, channel_weight: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
player_to_q[channel].put("UI:REMOVE:" + str(channel_weight))
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@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")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/autoadvance/<state:int>")
|
|
|
|
def player_autoadvance(request, channel: int, state: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
player_to_q[channel].put("UI:AUTOADVANCE:" + str(state))
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/repeat/<state:string>")
|
|
|
|
def player_repeat(request, channel: int, state: str):
|
2021-04-17 20:28:57 +00:00
|
|
|
player_to_q[channel].put("UI:REPEAT:" + state.upper())
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/playonload/<state:int>")
|
|
|
|
def player_playonload(request, channel: int, state: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
player_to_q[channel].put("UI:PLAYONLOAD:" + str(state))
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/player/<channel:int>/status")
|
|
|
|
def player_status_json(request, channel: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
return resp_json(status(channel))
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/player/all/stop")
|
2021-04-18 01:03:44 +00:00
|
|
|
def player_all_stop(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
for channel in player_to_q:
|
|
|
|
channel.put("UI:STOP")
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Show Plan Functions
|
|
|
|
|
2021-04-18 20:18:20 +00:00
|
|
|
@app.route("/plan/load/<timeslotid:int>")
|
2021-04-18 01:03:44 +00:00
|
|
|
def plan_load(request, timeslotid: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
for channel in player_to_q:
|
|
|
|
channel.put("UI:GET_PLAN:" + str(timeslotid))
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/plan/clear")
|
2021-04-18 01:03:44 +00:00
|
|
|
def plan_clear(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
for channel in player_to_q:
|
|
|
|
channel.put("UI:CLEAR")
|
2021-04-18 01:03:44 +00:00
|
|
|
return redirect("/status")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# API Proxy Endpoints
|
|
|
|
|
|
|
|
@app.route("/plan/list")
|
2021-04-18 02:14:14 +00:00
|
|
|
async def api_list_showplans(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
return resp_json(await api.get_showplans())
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
@app.route("/library/search/track")
|
|
|
|
async def api_search_library(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
return resp_json(await api.get_track_search(request.args.get("title"), request.args.get("artist")))
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/library/playlists/<type:string>")
|
2021-04-18 02:14:14 +00:00
|
|
|
async def api_get_playlists(request, type: str):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
if type not in ["music", "aux"]:
|
|
|
|
abort(401)
|
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
if type == "music":
|
|
|
|
return resp_json(await api.get_playlist_music())
|
|
|
|
else:
|
|
|
|
return resp_json(await api.get_playlist_aux())
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/library/playlist/<type:string>/<library_id:string>")
|
2021-04-18 02:14:14 +00:00
|
|
|
async def api_get_playlist(request, type: str, library_id: str):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
if type not in ["music", "aux"]:
|
|
|
|
abort(401)
|
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
if type == "music":
|
|
|
|
return resp_json(await api.get_playlist_music_items(library_id))
|
|
|
|
else:
|
|
|
|
return resp_json(await api.get_playlist_aux_items(library_id))
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# JSON Outputs
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/status-json")
|
2021-04-18 01:03:44 +00:00
|
|
|
def json_status(request):
|
2021-04-17 20:28:57 +00:00
|
|
|
channel_states = []
|
2021-04-18 19:27:54 +00:00
|
|
|
for i in range(server_state.get()["num_channels"]):
|
2021-04-17 20:28:57 +00:00
|
|
|
channel_states.append(status(i))
|
2021-04-18 19:27:54 +00:00
|
|
|
return resp_json({"server": server_state.get(), "channels": channel_states})
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Get audio for UI to generate waveforms.
|
|
|
|
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
@app.route("/audiofile/<type:string>/<id:int>")
|
|
|
|
async def audio_file(request, type: str, id: int):
|
2021-04-17 20:28:57 +00:00
|
|
|
if type not in ["managed", "track"]:
|
|
|
|
abort(404)
|
2021-08-16 22:43:09 +00:00
|
|
|
filename = resolve_external_file_path("music-tmp/{}-{}.mp3".format(type,id))
|
|
|
|
|
|
|
|
# Swap with a normalised version if it's ready, else returns original.
|
|
|
|
filename = get_normalised_filename_if_available(filename)
|
|
|
|
|
|
|
|
# Send file or 404
|
|
|
|
return await file(filename)
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Static Files
|
2021-04-18 02:52:34 +00:00
|
|
|
app.static("/favicon.ico", resolve_local_file_path("ui-static/favicon.ico"), name="ui-favicon")
|
|
|
|
app.static("/static", resolve_local_file_path("ui-static"), name="ui-static")
|
2021-08-16 22:43:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
dist_directory = resolve_local_file_path("presenter-build")
|
|
|
|
app.static('/presenter', dist_directory)
|
2021-05-15 22:51:12 +00:00
|
|
|
app.static("/presenter/", resolve_local_file_path("presenter-build/index.html"),
|
|
|
|
strict_slashes=True, name="presenter-index")
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Helper Functions
|
|
|
|
|
|
|
|
def status(channel: int):
|
|
|
|
while not player_from_q[channel].empty():
|
|
|
|
player_from_q[channel].get() # Just waste any previous status responses.
|
|
|
|
|
|
|
|
player_to_q[channel].put("UI:STATUS")
|
|
|
|
retries = 0
|
|
|
|
while retries < 40:
|
|
|
|
try:
|
|
|
|
response = player_from_q[channel].get_nowait()
|
|
|
|
if response.startswith("UI:STATUS:"):
|
|
|
|
response = response.split(":", 2)[2]
|
|
|
|
# TODO: Handle OKAY / FAIL
|
|
|
|
response = response[response.index(":") + 1:]
|
|
|
|
try:
|
|
|
|
response = json.loads(response)
|
|
|
|
except Exception as e:
|
|
|
|
raise e
|
|
|
|
return response
|
|
|
|
|
|
|
|
except Empty:
|
|
|
|
pass
|
|
|
|
|
|
|
|
retries += 1
|
|
|
|
|
|
|
|
sleep(0.02)
|
|
|
|
|
|
|
|
# WebServer Start / Stop Functions
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/quit")
|
2021-04-18 01:03:44 +00:00
|
|
|
def quit(request):
|
2021-04-18 19:27:54 +00:00
|
|
|
server_state.update("running_state", "quitting")
|
2021-04-18 21:23:25 +00:00
|
|
|
|
|
|
|
data = {
|
|
|
|
"ui_page": "message",
|
|
|
|
"ui_title": "Quitting BAPSicle",
|
|
|
|
"title": "See you later!",
|
|
|
|
"ui_menu": False,
|
|
|
|
"message": "BAPSicle is going back into winter hibernation, see you again soon!"
|
|
|
|
}
|
|
|
|
return render_template("message.html", data)
|
2021-04-18 19:27:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
@app.route("/restart")
|
|
|
|
def restart(request):
|
|
|
|
server_state.update("running_state", "restarting")
|
2021-04-18 21:23:25 +00:00
|
|
|
|
|
|
|
data = {
|
|
|
|
"ui_page": "message",
|
|
|
|
"ui_title": "Restarting BAPSicle",
|
|
|
|
"title": "Please Wait...",
|
|
|
|
"ui_menu": False,
|
|
|
|
"message": "Just putting BAPSicle back in the freezer for a moment!",
|
|
|
|
"redirect_to": "/",
|
|
|
|
"redirect_wait_ms": 10000
|
|
|
|
}
|
|
|
|
return render_template("message.html", data)
|
2021-04-17 20:28:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Don't use reloader, it causes Nested Processes!
|
2021-04-18 02:14:14 +00:00
|
|
|
def WebServer(player_to: List[Queue], player_from: List[Queue], state: StateManager):
|
2021-04-17 20:28:57 +00:00
|
|
|
|
2021-04-18 20:32:31 +00:00
|
|
|
global player_to_q, player_from_q, server_state, api, app
|
2021-04-17 20:28:57 +00:00
|
|
|
player_to_q = player_to
|
|
|
|
player_from_q = player_from
|
|
|
|
server_state = state
|
|
|
|
|
2021-04-18 02:14:14 +00:00
|
|
|
logger = LoggingManager("WebServer")
|
|
|
|
api = MyRadioAPI(logger, state)
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
process_title = "Web Server"
|
2021-04-17 20:28:57 +00:00
|
|
|
setproctitle(process_title)
|
|
|
|
CORS(app, supports_credentials=True) # Allow ALL CORS!!!
|
|
|
|
|
2021-04-18 01:03:44 +00:00
|
|
|
terminate = Terminator()
|
|
|
|
while not terminate.terminate:
|
|
|
|
try:
|
2021-04-18 02:52:34 +00:00
|
|
|
sync(app.run(
|
2021-04-18 19:27:54 +00:00
|
|
|
host=server_state.get()["host"],
|
|
|
|
port=server_state.get()["port"],
|
2021-04-19 14:45:20 +00:00
|
|
|
debug=(not isBundelled()),
|
|
|
|
auto_reload=False,
|
|
|
|
access_log=(not isBundelled())
|
2021-04-18 02:52:34 +00:00
|
|
|
))
|
2021-04-18 01:03:44 +00:00
|
|
|
except Exception:
|
|
|
|
break
|
2021-04-18 20:32:31 +00:00
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
if loop:
|
|
|
|
loop.close()
|
|
|
|
if app:
|
|
|
|
app.stop()
|
|
|
|
del app
|