From bdda886e10dbb39d77c1b7f2c90ab4e5c0264ece Mon Sep 17 00:00:00 2001 From: michael-grace Date: Sun, 8 Nov 2020 23:38:57 +0000 Subject: [PATCH 1/9] windows doesn't like the directories not existing --- .gitignore | 4 ++-- helpers/logging_manager.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 09936eb..b68e97c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ __pycache__/ -state/ +state/* *.egg-info/ @@ -31,7 +31,7 @@ dev/welcome.mp3 build/build-exe-pyinstaller-command.sh -logs/ +logs/* *.mp3 diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py index 8df6d65..c51d4a4 100644 --- a/helpers/logging_manager.py +++ b/helpers/logging_manager.py @@ -1,5 +1,6 @@ import logging from helpers.os_environment import resolve_external_file_path +import os class LoggingManager(): @@ -9,8 +10,19 @@ class LoggingManager(): def __init__(self, name): self.logger = logging.getLogger(name) + filename: str = resolve_external_file_path("/logs/" + name + ".log") + + if not os.path.isfile(filename): + try: + # Try creating the file. + open(filename, "x") + except Exception as e: + print(e) + print("Failed to create log file") + return + logging.basicConfig( - filename=resolve_external_file_path("/logs/" + name + ".log"), + filename=filename, format='%(asctime)s | %(levelname)s | %(message)s', level=logging.INFO, filemode='a' From b1e20eae5c305ec043e42cb67f3bc892b3ca27c2 Mon Sep 17 00:00:00 2001 From: michael-grace Date: Mon, 9 Nov 2020 00:10:36 +0000 Subject: [PATCH 2/9] server state --- config.py.example | 4 --- helpers/logging_manager.py | 3 +- helpers/state_manager.py | 18 +++++------ server.py | 63 ++++++++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 34 deletions(-) diff --git a/config.py.example b/config.py.example index f50f5ad..9925970 100644 --- a/config.py.example +++ b/config.py.example @@ -1,6 +1,2 @@ -# Flask Details -HOST: str = "localhost" -PORT: int = 13500 - # BAPSicle Details VERSION: float = 1.0 \ No newline at end of file diff --git a/helpers/logging_manager.py b/helpers/logging_manager.py index c51d4a4..0a11e25 100644 --- a/helpers/logging_manager.py +++ b/helpers/logging_manager.py @@ -16,8 +16,7 @@ class LoggingManager(): try: # Try creating the file. open(filename, "x") - except Exception as e: - print(e) + except: print("Failed to create log file") return diff --git a/helpers/state_manager.py b/helpers/state_manager.py index 40257b5..8e1b28c 100644 --- a/helpers/state_manager.py +++ b/helpers/state_manager.py @@ -19,8 +19,7 @@ class StateManager: __rate_limit_params_until = {} __rate_limit_period_s = 0 - - def __init__(self, name, logger: LoggingManager, default_state=None, rate_limit_params=[], rate_limit_period_s = 5): + def __init__(self, name, logger: LoggingManager, default_state=None, rate_limit_params=[], rate_limit_period_s=5): self.logger = logger self.filepath = resolve_external_file_path("/state/" + name + ".json") @@ -47,9 +46,10 @@ class StateManager: file_state = json.loads(file_state) # Turn from JSON -> PlanObject - file_state["loaded_item"] = PlanObject(file_state["loaded_item"]) if file_state["loaded_item"] else None - - file_state["show_plan"] = [PlanObject(obj) for obj in file_state["show_plan"]] + if "channel" in file_state: + file_state["loaded_item"] = PlanObject( + file_state["loaded_item"]) if file_state["loaded_item"] else None + file_state["show_plan"] = [PlanObject(obj) for obj in file_state["show_plan"]] # Now feed the loaded state into the initialised state manager. self.state = file_state @@ -73,7 +73,7 @@ class StateManager: def state(self, state): self.__state = copy(state) - def write_to_file(self,state): + def write_to_file(self, state): if self.__state_in_file == state: # No change to be updated. return @@ -89,8 +89,9 @@ class StateManager: state_to_json["last_updated"] = current_time # Not the biggest fan of this, but maybe I'll get a better solution for this later - state_to_json["loaded_item"] = state_to_json["loaded_item"].__dict__ if state_to_json["loaded_item"] else None - state_to_json["show_plan"] = [repr.__dict__ for repr in state_to_json["show_plan"]] + if "channel" in state_to_json: # If its a channel object + state_to_json["loaded_item"] = state_to_json["loaded_item"].__dict__ if state_to_json["loaded_item"] else None + state_to_json["show_plan"] = [repr.__dict__ for repr in state_to_json["show_plan"]] try: state_json = json.dumps(state_to_json, indent=2, sort_keys=True) except: @@ -109,7 +110,6 @@ class StateManager: else: self.__rate_limit_params_until[key] = self._currentTimeS + self.__rate_limit_period_s - state_to_update = self.state if key in state_to_update and state_to_update[key] == value: diff --git a/server.py b/server.py index b1c0e54..bd8e707 100644 --- a/server.py +++ b/server.py @@ -28,9 +28,19 @@ if not isMacOS(): import config from typing import Dict, List +from helpers.state_manager import StateManager +from helpers.logging_manager import LoggingManager setproctitle.setproctitle("BAPSicle - Server") +default_state = { + "server_version": 0, + "server_name": "URY BAPSicle", + "host": "localhost", + "port": 13500, + "num_channels": 3 +} + class BAPSicleServer(): @@ -46,6 +56,11 @@ class BAPSicleServer(): stopServer() +logger = LoggingManager("BAPSicleServer") + +state = StateManager("BAPSicleServer", logger, default_state) +state.update("server_version", config.VERSION) + app = Flask(__name__, static_url_path='') log = logging.getLogger('werkzeug') @@ -59,7 +74,7 @@ channel_p = [] stopping = False -### General Endpoints +# General Endpoints @app.errorhandler(404) def page_not_found(e): @@ -109,7 +124,8 @@ def ui_status(): } return render_template('status.html', data=data) -### Channel Audio Options +# Channel Audio Options + @app.route("/player//play") def play(channel): @@ -156,28 +172,33 @@ def output(channel, name): channel_to_q[channel].put("OUTPUT:" + name) return ui_status() + @app.route("/player//autoadvance/") def autoadvance(channel: int, state: int): channel_to_q[channel].put("AUTOADVANCE:" + str(state)) return ui_status() + @app.route("/player//repeat/") def repeat(channel: int, state): channel_to_q[channel].put("REPEAT:" + state.upper()) return ui_status() + @app.route("/player//playonload/") def playonload(channel: int, state: int): channel_to_q[channel].put("PLAYONLOAD:" + str(state)) return ui_status() -### Channel Items +# Channel Items + @app.route("/player//load/") -def load(channel:int, timeslotitemid: int): +def load(channel: int, timeslotitemid: int): channel_to_q[channel].put("LOAD:" + str(timeslotitemid)) return ui_status() + @app.route("/player//unload") def unload(channel): @@ -185,6 +206,7 @@ def unload(channel): return ui_status() + @app.route("/player//add", methods=["POST"]) def add_to_plan(channel: int): new_item: Dict[str, any] = { @@ -198,28 +220,32 @@ def add_to_plan(channel: int): return new_item + @app.route("/player//move//") def move_plan(channel: int, timeslotitemid: int, position: int): channel_to_q[channel].put("MOVE:" + json.dumps({"timeslotitemid": timeslotitemid, "position": position})) - #TODO Return + # TODO Return return True + @app.route("/player//remove/") def remove_plan(channel: int, timeslotitemid: int): channel_to_q[channel].put("REMOVE:" + timeslotitemid) - #TODO Return + # TODO Return return True + @app.route("/player//clear") def clear_channel_plan(channel: int): channel_to_q[channel].put("CLEAR") - #TODO Return + # TODO Return return True -### General Channel Endpoints +# General Channel Endpoints + @app.route("/player//status") def status(channel): @@ -266,7 +292,7 @@ def send_static(path): def startServer(): if isMacOS(): multiprocessing.set_start_method("spawn", True) - for channel in range(3): + for channel in range(state.state["num_channels"]): channel_to_q.append(multiprocessing.Queue()) channel_from_q.append(multiprocessing.Queue()) @@ -287,14 +313,13 @@ def startServer(): text_to_speach = pyttsx3.init() text_to_speach.save_to_file( - """Thank-you for installing BAPSicle - the play-out server from the broadcasting and presenting suite. - This server is accepting connections on port {0} - The version of the server service is {1} + """Thank-you for installing BAPSicle - the play-out server from the broadcasting and presenting suite. + This server is accepting connections on port 13500 + The version of the server service is {} Please refer to the documentation included with this application for further assistance.""".format( - config.PORT, - config.VERSION - ), - "dev/welcome.mp3" + config.VERSION + ), + "dev/welcome.mp3" ) text_to_speach.runAndWait() @@ -306,12 +331,12 @@ def startServer(): } channel_to_q[0].put("ADD:" + json.dumps(new_item)) - #channel_to_q[0].put("LOAD:0") - #channel_to_q[0].put("PLAY") + # channel_to_q[0].put("LOAD:0") + # channel_to_q[0].put("PLAY") # Don't use reloader, it causes Nested Processes! + app.run(host=state.state["host"], port=state.state["port"], debug=True, use_reloader=False) - app.run(host='0.0.0.0', port=13500, debug=True, use_reloader=False) def stopServer(): print("Stopping server.py") From 26a677be537db061c69e0005eb204ef007e0839a Mon Sep 17 00:00:00 2001 From: michael-grace Date: Mon, 9 Nov 2020 00:17:48 +0000 Subject: [PATCH 3/9] pure json status page --- server.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index bd8e707..832a719 100644 --- a/server.py +++ b/server.py @@ -114,7 +114,7 @@ def ui_config(): @app.route("/status") def ui_status(): channel_states = [] - for i in range(3): + for i in range(state.state["num_channels"]): channel_states.append(status(i)) data = { @@ -124,6 +124,17 @@ def ui_status(): } return render_template('status.html', data=data) + +@app.route("/status-json") +def json_status(): + channel_states = [] + for i in range(state.state["num_channels"]): + channel_states.append(status(i)) + return { + "server": state.state, + "channels": channel_states + } + # Channel Audio Options From 7e57d2de36bc4b4aae0e2225167d760a7359c481 Mon Sep 17 00:00:00 2001 From: michael-grace Date: Mon, 9 Nov 2020 00:42:09 +0000 Subject: [PATCH 4/9] viewing logs in webserver --- server.py | 22 ++++++++++++++++++++++ templates/log.html | 11 +++++++++++ templates/loglist.html | 9 +++++++++ 3 files changed, 42 insertions(+) create mode 100644 templates/log.html create mode 100644 templates/loglist.html diff --git a/server.py b/server.py index 832a719..f6807fe 100644 --- a/server.py +++ b/server.py @@ -300,6 +300,28 @@ def send_static(path): return send_from_directory('ui-static', path) +@app.route("/logs") +def list_logs(): + data = { + "ui_page": "loglist", + "ui_title": "Logs", + "logs": ["BAPSicleServer"] + ["channel{}".format(x) for x in range(state.state["num_channels"])] + } + return render_template("loglist.html", data=data) + + +@app.route("/logs/") +def send_logs(path): + l = open("logs/{}.log".format(path)) + data = { + "logs": l.read().splitlines(), + 'ui_page': "log", + "ui_title": "Logs - {}".format(path) + } + l.close() + return render_template('log.html', data=data) + + def startServer(): if isMacOS(): multiprocessing.set_start_method("spawn", True) diff --git a/templates/log.html b/templates/log.html new file mode 100644 index 0000000..5651a39 --- /dev/null +++ b/templates/log.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% block content_inner %} + {% if data %} + {% for log in data.logs %} + + {{log}} + +
+ {% endfor %} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/loglist.html b/templates/loglist.html new file mode 100644 index 0000000..7ae36b9 --- /dev/null +++ b/templates/loglist.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} +{% block content_inner %} + {% if data %} + {% for log in data.logs %} + {{log}} +
+ {% endfor %} + {% endif %} +{% endblock %} \ No newline at end of file From f1cd401b3be80df7b80b02aa1e46b55844f41264 Mon Sep 17 00:00:00 2001 From: michael-grace Date: Tue, 10 Nov 2020 18:45:54 +0000 Subject: [PATCH 5/9] keep the folders to keep windows happy --- .gitignore | 3 --- logs/.gitignore | 1 + state/.gitignore | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 logs/.gitignore create mode 100644 state/.gitignore diff --git a/.gitignore b/.gitignore index b68e97c..d7ee597 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ __pycache__/ -state/* *.egg-info/ @@ -31,8 +30,6 @@ dev/welcome.mp3 build/build-exe-pyinstaller-command.sh -logs/* - *.mp3 *.oga diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..bf0824e --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/state/.gitignore b/state/.gitignore new file mode 100644 index 0000000..94a2dd1 --- /dev/null +++ b/state/.gitignore @@ -0,0 +1 @@ +*.json \ No newline at end of file From 53d7edba487343ea19eff63009118581c5555854 Mon Sep 17 00:00:00 2001 From: michael-grace Date: Tue, 10 Nov 2020 18:49:26 +0000 Subject: [PATCH 6/9] server details on web interface --- server.py | 4 +++- templates/index.html | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index f6807fe..9504170 100644 --- a/server.py +++ b/server.py @@ -89,7 +89,9 @@ def page_not_found(e): def ui_index(): data = { 'ui_page': "index", - "ui_title": "" + "ui_title": "", + "server_version": config.VERSION, + "server_name": state.state["server_name"] } return render_template('index.html', data=data) diff --git a/templates/index.html b/templates/index.html index a999a62..ac2e7d4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,7 +17,7 @@ Open WebStudio
-

Version: X | Server Name: Studio X

+

Version: {{data.server_version}} | Server Name: {{data.server_name}}

From ba07fa5d89bf052c58fc73f71328f6304abba896 Mon Sep 17 00:00:00 2001 From: michael-grace Date: Tue, 10 Nov 2020 19:40:42 +0000 Subject: [PATCH 7/9] server changing and restarting --- server.py | 34 +++++++++++++++++++++++++++++++--- templates/base.html | 6 ++++-- templates/server.html | 20 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 templates/server.html diff --git a/server.py b/server.py index 9504170..e1331ba 100644 --- a/server.py +++ b/server.py @@ -99,7 +99,7 @@ def ui_index(): @app.route("/config") def ui_config(): channel_states = [] - for i in range(3): + for i in range(state.state["num_channels"]): channel_states.append(status(i)) outputs = DeviceManager.getOutputs() @@ -137,6 +137,26 @@ def json_status(): "channels": channel_states } + +@app.route("/server") +def server_config(): + data = { + "ui_page": "server", + "ui_title": "Server Config", + "state": state.state + } + return render_template("server.html", data=data) + + +@app.route("/restart", methods=["POST"]) +def restart_server(): + state.update("server_name", request.form["name"]) + state.update("host", request.form["host"]) + state.update("port", int(request.form["port"])) + state.update("num_channels", int(request.form["channels"])) + stopServer(restart=True) + startServer() + # Channel Audio Options @@ -373,7 +393,10 @@ def startServer(): app.run(host=state.state["host"], port=state.state["port"], debug=True, use_reloader=False) -def stopServer(): +def stopServer(restart=False): + global channel_p + global channel_from_q + global channel_to_q print("Stopping server.py") for q in channel_to_q: q.put("QUIT") @@ -382,6 +405,10 @@ def stopServer(): player.join() except: pass + finally: + channel_p = [] + channel_from_q = [] + channel_to_q = [] print("Stopped all players.") global stopping if stopping == False: @@ -392,7 +419,8 @@ def stopServer(): else: print("Shutting down Flask.") - shutdown() + if not restart: + shutdown() if __name__ == "__main__": diff --git a/templates/base.html b/templates/base.html index 3b4a82d..fa69d87 100644 --- a/templates/base.html +++ b/templates/base.html @@ -8,7 +8,6 @@ {% block head %} - {% endblock %} BAPSicle {% if data.ui_title %} | {{data.ui_title}}{% endif %} @@ -40,7 +39,10 @@ Status - Config + Channel Config + + + Server Config Logs diff --git a/templates/server.html b/templates/server.html new file mode 100644 index 0000000..c006006 --- /dev/null +++ b/templates/server.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% block content_inner %} + {% if data %} +
+ + +
+ + +
+ + +
+ + +
+ +
+ {% endif %} +{% endblock %} \ No newline at end of file From b00ac8ecbf5e74b94fa7cb2189e412df51cf3e2a Mon Sep 17 00:00:00 2001 From: michael-grace Date: Tue, 10 Nov 2020 19:41:27 +0000 Subject: [PATCH 8/9] welcome default --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index e1331ba..488bcd2 100644 --- a/server.py +++ b/server.py @@ -369,7 +369,7 @@ def startServer(): text_to_speach = pyttsx3.init() text_to_speach.save_to_file( """Thank-you for installing BAPSicle - the play-out server from the broadcasting and presenting suite. - This server is accepting connections on port 13500 + By defulat, this server is accepting connections on port 13500 The version of the server service is {} Please refer to the documentation included with this application for further assistance.""".format( config.VERSION From 5c19d5ca043f9b9faae26c400638e284c10a3a6b Mon Sep 17 00:00:00 2001 From: Michael Grace <56653532+michael-grace@users.noreply.github.com> Date: Tue, 10 Nov 2020 21:38:02 +0000 Subject: [PATCH 9/9] typo i can't type :) --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index 488bcd2..b108652 100644 --- a/server.py +++ b/server.py @@ -369,7 +369,7 @@ def startServer(): text_to_speach = pyttsx3.init() text_to_speach.save_to_file( """Thank-you for installing BAPSicle - the play-out server from the broadcasting and presenting suite. - By defulat, this server is accepting connections on port 13500 + By default, this server is accepting connections on port 13500 The version of the server service is {} Please refer to the documentation included with this application for further assistance.""".format( config.VERSION