Merge branch 'dev' into debugging

This commit is contained in:
Matthew Stratford 2020-11-01 03:02:51 +00:00
commit 6e79af3bbd
No known key found for this signature in database
GPG key ID: 9E53C8B3F0B57395
18 changed files with 5481 additions and 37 deletions

2
.gitignore vendored
View file

@ -21,6 +21,8 @@ build/build/BAPSicle/
build/output/ build/output/
build/build-exe-pyinstaller-command.sh
logs/ logs/
*.mp3 *.mp3

View file

@ -1,7 +1,7 @@
# BAPSicle # BAPSicle
### a.k.a. The Next-Gen BAPS server ### a.k.a. The Next-Gen BAPS server
!["BAPSicle logo, a pink melting ice lolly."](/dev/logo.png "BAPSicle Logo") !["BAPSicle logo, a pink melting ice lolly."](docs/images/logo.png "BAPSicle Logo")
Welcome! This is BAPS. More acurately, this is yet another attempt at a BAPS3 server. Welcome! This is BAPS. More acurately, this is yet another attempt at a BAPS3 server.
@ -16,32 +16,48 @@ Currently there's just a batch script. Simply run ``install.bat`` as administrat
This will: This will:
* Copy BAPSicle into ``C:\Program Files\BAPSicle`` * Copy BAPSicle into ``C:\Program Files\BAPSicle``
* Install BAPSicle.exe as a Windows Service with NSSM. * Install BAPSicle.exe as a Windows Service with NSSM.
* If all goes well, open [http://localhost:5000](localhost:5000) for the server UI. * If all goes well, open [http://localhost:13500](localhost:13500) for the server UI.
### Linux ### Linux
Installed service for linux is comming soon. Testing is primarily on Ubuntu 20.04. Your milage with other distros will vary. Installed service for linux is coming soon. Testing is primarily on Ubuntu 20.04. Your milage with other distros will vary.
### MacOS ### MacOS
Support for MacOS will be the last to come, sorry about that. Currently there's no installer for MacOS, so you'll have to move the ``build/output/BAPSicle.app`` you've built and make it start automatically (if you want).
Starting and stopping the server, as well as UI links, are available in the System Menu once opening the app.
!["BAPSicle in the MacOS System Menu"](docs/images/system-menu.png "System Menu")
## Developing ## Developing
### Requirements ### Requirements
On all platforms:
* Python 3.7 (3.8 may also work, 3.9 is unlikely to.) * Python 3.7 (3.8 may also work, 3.9 is unlikely to.)
* Git (Obviously) * Git (Obviously)
On MacOS:
* Homebrew (To install command line Platypus)
### Running ### Running
To just run the server standaline without installing, run ``python ./launch_standalone.py``. To just run the server standaline without installing, run ``python ./launch_standalone.py``.
### Building ## Building
Currently mostly Windows focused. ### Windows
To build a BAPSicle.exe, run ``build\build-exe.py``. The resulting file will appear in ``build\output``. You can then use the install instructions above to install it, or just run it standalone. To build a ``BAPSicle.exe``, run ``build\build-exe.bat``. The resulting file will appear in ``build\output``. You can then use the install instructions above to install it, or just run it standalone.
### Linux
Coming soon...
### MacOS
To build a ``BAPSicle.app``, run ``build/build-macos.sh``. The resulting file will appear in ``build/output``.
### Other bits ### Other bits
Provided is a VScode debug config to let you debug live, as well as ``dev\install-githook.bat`` that will help git to clean your code up as you're committing! Provided is a VScode debug config to let you debug live, as well as ``dev\install-githook.{bat,sh}`` that will help git to clean your code up as you're committing!

5297
build/BAPSicle.platypus Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@
}, },
{ {
"optionDest": "filenames", "optionDest": "filenames",
"value": "\\launch_standalone.py" "value": "/launch_standalone.py"
}, },
{ {
"optionDest": "onefile", "optionDest": "onefile",
@ -15,11 +15,15 @@
}, },
{ {
"optionDest": "console", "optionDest": "console",
"value": true "value": false
},
{
"optionDest": "exclude-module",
"value": "tkinter"
}, },
{ {
"optionDest": "icon_file", "optionDest": "icon_file",
"value": "\\build\\icon.ico" "value": "/build/icon.ico"
}, },
{ {
"optionDest": "name", "optionDest": "name",
@ -63,15 +67,15 @@
}, },
{ {
"optionDest": "datas", "optionDest": "datas",
"value": "\\templates;templates/" "value": "/templates;templates/"
}, },
{ {
"optionDest": "datas", "optionDest": "datas",
"value": "\\ui-static;ui-static/" "value": "/ui-static;ui-static/"
} }
], ],
"nonPyinstallerOptions": { "nonPyinstallerOptions": {
"increaseRecursionLimit": false, "increaseRecursionLimit": false,
"manualArguments": "" "manualArguments": ""
} }
} }

View file

@ -32,12 +32,13 @@ for option in config["pyinstallerOptions"]:
cmd_str += "--" + str(option_dest) + ' "' + str(option["value"]) + '" ' cmd_str += "--" + str(option_dest) + ' "' + str(option["value"]) + '" '
command = open('build-exe-pyinstaller-command.bat', 'w') for format in [".bat", ".sh"]:
command = open('build-exe-pyinstaller-command'+format, 'w')
if filename == "": if filename == "":
print("No filename data was found in json file.") print("No filename data was found in json file.")
command.write("") command.write("")
else: else:
command.write(cmd_str + ' --distpath "output/" --workpath "build/" "' + filename + '"') command.write(cmd_str + ' --distpath "output/" --workpath "build/" "' + filename + '"')
command.close() command.close()

19
build/build-macos.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
cd "$(dirname "$0")"
pip3 install -r requirements.txt
pip3 install -r requirements-macos.txt
pip3 install -e ..\
python3 ./generate-build-exe-config.py
python3 ./build-exe.py
bash ./build-exe-pyinstaller-command.sh
rm ./*.spec
brew install platypus
platypus --load-profile ./BAPSicle.platypus --overwrite ./output/BAPSicle.app

View file

@ -1,5 +1,6 @@
import json import json
import os import os
from helpers.os_environment import isWindows
dir_path = os.path.dirname(os.path.realpath(__file__)) dir_path = os.path.dirname(os.path.realpath(__file__))
parent_path = os.path.dirname(dir_path) parent_path = os.path.dirname(dir_path)
@ -11,6 +12,8 @@ in_file.close()
for option in config["pyinstallerOptions"]: for option in config["pyinstallerOptions"]:
if option["optionDest"] in ["datas", "filenames", "icon_file"]: if option["optionDest"] in ["datas", "filenames", "icon_file"]:
option["value"] = os.path.abspath(parent_path + option["value"]) option["value"] = os.path.abspath(parent_path + option["value"])
if not isWindows():
option["value"] = option["value"].replace(";",":")
out_file = open('build-exe-config.json', 'w') out_file = open('build-exe-config.json', 'w')
out_file.write(json.dumps(config, indent=2)) out_file.write(json.dumps(config, indent=2))

BIN
build/icon.icns Normal file

Binary file not shown.

BIN
build/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

27
build/macos-platypus.sh Normal file
View file

@ -0,0 +1,27 @@
#!/bin/bash
if [ "$1" == "" ]
then
echo "DISABLED|BAPSicle Server"
echo "----"
if curl --output /dev/null --silent --head --fail --max-time 1 "http://localhost:13500"
then
echo "Status"
echo "Config"
echo "Logs"
echo "----"
echo "Stop Server"
else
echo "DISABLED|Status"
echo "DISABLED|Config"
echo "DISABLED|Logs"
echo "----"
echo "Start Server"
fi
exit
fi
if [ "$1" == "Stop Server" ]
then
curl "http://localhost:13500/quit"
else
./BAPSicle "$1"
fi

View file

@ -0,0 +1 @@
pyinstaller

4
dev/install-githook.sh Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
cd "$(dirname "$0")"
cp "./pre-commit" "../.git/hooks/"

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/images/system-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -28,7 +28,7 @@ nssm set %service_name% AppStopMethodConsole 5000
nssm set %service_name% AppStopMethodWindow 5000 nssm set %service_name% AppStopMethodWindow 5000
nssm set %service_name% AppStopMethodThreads 5000 nssm set %service_name% AppStopMethodThreads 5000
nssm set %service_name% DisplayName "BAPSicle Server" nssm set %service_name% DisplayName "BAPSicle Server"
nssm set %service_name% Description "The next gen Broadcast and Presenting Suite server! Access settings on port 5000." nssm set %service_name% Description "The next gen Broadcast and Presenting Suite server! Access settings on port 13500."
nssm set %service_name% ObjectName LocalSystem nssm set %service_name% ObjectName LocalSystem
nssm set %service_name% Start SERVICE_AUTO_START nssm set %service_name% Start SERVICE_AUTO_START
nssm set %service_name% Type SERVICE_INTERACTIVE_PROCESS nssm set %service_name% Type SERVICE_INTERACTIVE_PROCESS
@ -41,4 +41,4 @@ nssm start %service_name%
timeout 4 /nobreak timeout 4 /nobreak
explorer "http://localhost:5000/" explorer "http://localhost:13500/"

View file

@ -1,13 +1,48 @@
import multiprocessing import multiprocessing
import time import time
import sys
import webbrowser
from server import BAPSicleServer from server import BAPSicleServer
if __name__ == '__main__':
# On Windows calling this function is necessary. def startServer():
# Causes all kinds of loops if not present. server = multiprocessing.Process(target=BAPSicleServer)
multiprocessing.freeze_support() server.start()
server = multiprocessing.Process(target=BAPSicleServer).start()
while True: while True:
time.sleep(1) time.sleep(5)
pass if server and server.is_alive():
pass
else:
print("Server dead. Exiting.")
sys.exit(0)
if __name__ == '__main__':
# On Windows, calling this function is necessary.
# Causes all kinds of loops if not present.
# IT HAS TO BE RIGHT HERE, AT THE TOP OF __MAIN__
# NOT INSIDE AN IF STATEMENT. RIGHT. HERE.
# If it's not here, multiprocessing just doesn't run in the package.
# Freeze support refers to being packaged with Pyinstaller.
multiprocessing.freeze_support()
if len(sys.argv) > 1:
# We got an argument! It's probably Platypus's UI.
try:
if (sys.argv[1]) == "Start Server":
print("NOTIFICATION:Welcome to BAPSicle!")
webbrowser.open("http://localhost:13500/")
startServer()
if (sys.argv[1] == "Status"):
webbrowser.open("http://localhost:13500/status")
if (sys.argv[1] == "Config"):
webbrowser.open("http://localhost:13500/config")
if (sys.argv[1] == "Logs"):
webbrowser.open("http://localhost:13500/logs")
except Exception as e:
print("ALERT:BAPSicle failed with exception:\n", e)
sys.exit(0)
else:
startServer()

View file

@ -10,16 +10,15 @@ import setproctitle
import copy import copy
import json import json
import time import time
import sys
# Stop the Pygame Hello message. # Stop the Pygame Hello message.
import os import os
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
from pygame import mixer from pygame import mixer
from mutagen.mp3 import MP3
from helpers.state_manager import StateManager from helpers.state_manager import StateManager
from helpers.logging_manager import LoggingManager from helpers.logging_manager import LoggingManager
from mutagen.mp3 import MP3
class Player(): class Player():
state = None state = None
@ -155,6 +154,11 @@ class Player():
def load(self, filename): def load(self, filename):
if not self.isPlaying: if not self.isPlaying:
self.unload() self.unload()
# Fix any OS specific / or \'s
if os.path.sep == "/":
filename = filename.replace("\\", '/')
else:
filename = filename.replace("/", '\\')
self.state.update("filename", filename) self.state.update("filename", filename)
@ -346,6 +350,7 @@ class Player():
self.logger.log.info("Quiting player ", channel) self.logger.log.info("Quiting player ", channel)
self.quit() self.quit()
self._retMsg("EXIT") self._retMsg("EXIT")
sys.exit(0)
def showOutput(in_q, out_q): def showOutput(in_q, out_q):
@ -357,6 +362,8 @@ def showOutput(in_q, out_q):
if __name__ == "__main__": if __name__ == "__main__":
if isMacOS:
multiprocessing.set_start_method("spawn", True)
in_q = multiprocessing.Queue() in_q = multiprocessing.Queue()
out_q = multiprocessing.Queue() out_q = multiprocessing.Queue()

View file

@ -4,6 +4,8 @@ from flask import Flask, render_template, send_from_directory, request
import json import json
import sounddevice as sd import sounddevice as sd
import setproctitle import setproctitle
import logging
from helpers.os_environment import isMacOS
setproctitle.setproctitle("BAPSicle - Server") setproctitle.setproctitle("BAPSicle - Server")
@ -24,10 +26,15 @@ class BAPSicleServer():
app = Flask(__name__, static_url_path='') app = Flask(__name__, static_url_path='')
log = logging.getLogger('werkzeug')
log.disabled = True
app.logger.disabled = True
channel_to_q = [] channel_to_q = []
channel_from_q = [] channel_from_q = []
channel_p = [] channel_p = []
stopping = False
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
@ -154,6 +161,12 @@ def status(channel):
return response return response
@app.route("/quit")
def quit():
stopServer()
return "Shutting down..."
@app.route("/player/all/stop") @app.route("/player/all/stop")
def all_stop(): def all_stop():
for channel in channel_to_q: for channel in channel_to_q:
@ -167,7 +180,10 @@ def send_static(path):
def startServer(): def startServer():
if isMacOS():
multiprocessing.set_start_method("spawn", True)
for channel in range(3): for channel in range(3):
channel_to_q.append(multiprocessing.Queue()) channel_to_q.append(multiprocessing.Queue())
channel_from_q.append(multiprocessing.Queue()) channel_from_q.append(multiprocessing.Queue())
channel_p.append( channel_p.append(
@ -180,7 +196,7 @@ def startServer():
channel_p[channel].start() channel_p[channel].start()
# Don't use reloader, it causes Nested Processes! # Don't use reloader, it causes Nested Processes!
app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False) app.run(host='0.0.0.0', port=13500, debug=True, use_reloader=False)
def stopServer(): def stopServer():
@ -188,9 +204,21 @@ def stopServer():
for q in channel_to_q: for q in channel_to_q:
q.put("QUIT") q.put("QUIT")
for player in channel_p: for player in channel_p:
player.join() try:
global app player.join()
app = None except:
pass
print("Stopped all players.")
global stopping
if stopping == False:
stopping = True
shutdown = request.environ.get('werkzeug.server.shutdown')
if shutdown is None:
print("Shutting down Server.")
else:
print("Shutting down Flask.")
shutdown()
if __name__ == "__main__": if __name__ == "__main__":