home redirect + http port configurable

This commit is contained in:
boyska 2019-06-25 13:43:01 +02:00
parent 6ffeadc576
commit 0a7cad0e2d
3 changed files with 140 additions and 126 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
'''
"""
Taken from flask-appconfig
'''
"""
import json
import os
@ -9,30 +9,32 @@ import os
from xdg import BaseDirectory
def get_conf(prefix='LARIGIRA_'):
'''This is where everyone should get configuration from'''
conf_dir = BaseDirectory.save_config_path('larigira')
def get_conf(prefix="LARIGIRA_"):
"""This is where everyone should get configuration from"""
conf_dir = BaseDirectory.save_config_path("larigira")
conf = {}
conf['CONTINOUS_AUDIOSPEC'] = dict(kind='mpd', howmany=1)
conf['MPD_HOST'] = os.getenv('MPD_HOST', 'localhost')
conf['MPD_PORT'] = int(os.getenv('MPD_PORT', '6600'))
conf['CACHING_TIME'] = 10
conf['DB_URI'] = os.path.join(conf_dir, 'db.json')
conf['SCRIPTS_PATH'] = os.path.join(conf_dir, 'scripts')
conf['ROUTE_PREFIX'] = ''
conf['BOOTSTRAP_SERVE_LOCAL'] = True
conf['SECRET_KEY'] = 'Please replace me!'
conf['MPD_WAIT_START'] = True
conf['MPD_WAIT_START_RETRYSECS'] = 5
conf['CHECK_SECS'] = 20 # period for checking playlist length
conf['EVENT_TICK_SECS'] = 30 # period for scheduling events
conf['DEBUG'] = False
conf['LOG_CONFIG'] = False
conf['TMPDIR'] = os.getenv('TMPDIR', '/tmp/')
conf['FILE_PATH_SUGGESTION'] = () # tuple of paths
conf['UI_CALENDAR_FREQUENCY_THRESHOLD'] = 4*60*60 # 4 hours
conf['UI_CALENDAR_DATE_FMT'] = 'medium'
conf['EVENT_FILTERS'] = []
conf["CONTINOUS_AUDIOSPEC"] = dict(kind="mpd", howmany=1)
conf["MPD_HOST"] = os.getenv("MPD_HOST", "localhost")
conf["MPD_PORT"] = int(os.getenv("MPD_PORT", "6600"))
conf["CACHING_TIME"] = 10
conf["DB_URI"] = os.path.join(conf_dir, "db.json")
conf["SCRIPTS_PATH"] = os.path.join(conf_dir, "scripts")
conf["ROUTE_PREFIX"] = ""
conf["BOOTSTRAP_SERVE_LOCAL"] = True
conf["SECRET_KEY"] = "Please replace me!"
conf["MPD_WAIT_START"] = True
conf["MPD_WAIT_START_RETRYSECS"] = 5
conf["CHECK_SECS"] = 20 # period for checking playlist length
conf["EVENT_TICK_SECS"] = 30 # period for scheduling events
conf["DEBUG"] = False
conf["LOG_CONFIG"] = False
conf["TMPDIR"] = os.getenv("TMPDIR", "/tmp/")
conf["FILE_PATH_SUGGESTION"] = () # tuple of paths
conf["UI_CALENDAR_FREQUENCY_THRESHOLD"] = 4 * 60 * 60 # 4 hours
conf["UI_CALENDAR_DATE_FMT"] = "medium"
conf["EVENT_FILTERS"] = []
conf["HTTP_PORT"] = 5000
conf["HOME_URL"] = "/db/calendar"
conf.update(from_envvars(prefix=prefix))
return conf
@ -54,15 +56,16 @@ def from_envvars(prefix=None, envvars=None, as_json=True):
"""
conf = {}
if prefix is None and envvars is None:
raise RuntimeError('Must either give prefix or envvars argument')
raise RuntimeError("Must either give prefix or envvars argument")
# if it's a list, convert to dict
if isinstance(envvars, list):
envvars = {k: None for k in envvars}
if not envvars:
envvars = {k: k[len(prefix):] for k in os.environ.keys()
if k.startswith(prefix)}
envvars = {
k: k[len(prefix) :] for k in os.environ.keys() if k.startswith(prefix)
}
for env_name, name in envvars.items():
if name is None:

View file

@ -1,8 +1,9 @@
'''
"""
This module is for the main application logic
'''
"""
from __future__ import print_function
from gevent import monkey
monkey.patch_all(subprocess=True)
import sys
@ -29,12 +30,13 @@ def on_main_crash(*args, **kwargs):
class Larigira(object):
def __init__(self):
self.log = logging.getLogger('larigira')
self.log = logging.getLogger("larigira")
self.conf = get_conf()
self.controller = Controller(self.conf)
self.controller.link_exception(on_main_crash)
self.http_server = WSGIServer(('', 5000),
create_app(self.controller.q, self))
self.http_server = WSGIServer(
("", int(self.conf["HTTP_PORT"])), create_app(self.controller.q, self)
)
def start(self):
self.controller.start()
@ -42,11 +44,11 @@ class Larigira(object):
def sd_notify(ready=False, status=None):
args = ['systemd-notify']
args = ["systemd-notify"]
if ready:
args.append('--ready')
args.append("--ready")
if status is not None:
args.append('--status')
args.append("--status")
args.append(status)
try:
subprocess.check_call(args)
@ -55,43 +57,46 @@ def sd_notify(ready=False, status=None):
def main():
tempfile.tempdir = os.environ['TMPDIR'] = os.path.join(
os.getenv('TMPDIR', '/tmp'),
'larigira.%d' % os.getuid())
if not os.path.isdir(os.environ['TMPDIR']):
os.makedirs(os.environ['TMPDIR'])
if get_conf()['LOG_CONFIG']:
logging.config.fileConfig(get_conf()['LOG_CONFIG'],
disable_existing_loggers=True)
tempfile.tempdir = os.environ["TMPDIR"] = os.path.join(
os.getenv("TMPDIR", "/tmp"), "larigira.%d" % os.getuid()
)
if not os.path.isdir(os.environ["TMPDIR"]):
os.makedirs(os.environ["TMPDIR"])
if get_conf()["LOG_CONFIG"]:
logging.config.fileConfig(
get_conf()["LOG_CONFIG"], disable_existing_loggers=True
)
else:
log_format = '%(asctime)s|%(levelname)s[%(name)s:%(lineno)d] %(message)s'
logging.basicConfig(level=logging.DEBUG if get_conf()['DEBUG'] else logging.INFO,
format=log_format,
datefmt='%H:%M:%S')
if(get_conf()['MPD_WAIT_START']):
log_format = "%(asctime)s|%(levelname)s[%(name)s:%(lineno)d] %(message)s"
logging.basicConfig(
level=logging.DEBUG if get_conf()["DEBUG"] else logging.INFO,
format=log_format,
datefmt="%H:%M:%S",
)
if get_conf()["MPD_WAIT_START"]:
while True:
try:
get_mpd_client(get_conf())
except Exception:
logging.debug("Could not connect to MPD, waiting")
sd_notify(status='Waiting MPD connection')
sleep(int(get_conf()['MPD_WAIT_START_RETRYSECS']))
sd_notify(status="Waiting MPD connection")
sleep(int(get_conf()["MPD_WAIT_START_RETRYSECS"]))
else:
logging.info("MPD ready!")
sd_notify(ready=True, status='Ready')
sd_notify(ready=True, status="Ready")
break
larigira = Larigira()
larigira.start()
def sig(*args):
print('invoked sig', args)
larigira.controller.q.put(dict(kind='signal', args=args))
print("invoked sig", args)
larigira.controller.q.put(dict(kind="signal", args=args))
for signum in (signal.SIGHUP, signal.SIGALRM):
gevent.signal(signum, sig, signum)
gevent.wait()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -3,8 +3,16 @@ import gc
from copy import deepcopy
from greenlet import greenlet
from flask import current_app, Blueprint, Flask, jsonify, render_template, \
request, abort
from flask import (
current_app,
Blueprint,
Flask,
jsonify,
render_template,
request,
abort,
redirect,
)
from flask_bootstrap import Bootstrap
from flask.ext.babel import Babel
from werkzeug.contrib.cache import SimpleCache
@ -12,79 +20,80 @@ from werkzeug.contrib.cache import SimpleCache
from .dbadmin import db
from .config import get_conf
rpc = Blueprint('rpc', __name__,
url_prefix=get_conf()['ROUTE_PREFIX'] + '/api')
viewui = Blueprint('view', __name__,
url_prefix=get_conf()['ROUTE_PREFIX'] + '/view',
template_folder='templates')
rpc = Blueprint("rpc", __name__, url_prefix=get_conf()["ROUTE_PREFIX"] + "/api")
viewui = Blueprint(
"view",
__name__,
url_prefix=get_conf()["ROUTE_PREFIX"] + "/view",
template_folder="templates",
)
def send_to_parent(kind, *args):
'''similar to the behaviour of a ParentedLet'''
if not hasattr(current_app, 'queue'):
logging.debug('no parent queue; aborting send')
"""similar to the behaviour of a ParentedLet"""
if not hasattr(current_app, "queue"):
logging.debug("no parent queue; aborting send")
return
msg = {
'emitter': current_app._get_current_object(),
'class': current_app._get_current_object().__class__.__name__,
'kind': kind,
'args': args
"emitter": current_app._get_current_object(),
"class": current_app._get_current_object().__class__.__name__,
"kind": kind,
"args": args,
}
current_app.queue.put(msg)
@rpc.route('/')
@rpc.route("/")
def rpc_index():
rules = list(current_app.url_map.iter_rules())
return jsonify({'rules': [r.rule for r in rules
if r.rule.startswith('/api')]})
return jsonify({"rules": [r.rule for r in rules if r.rule.startswith("/api")]})
@rpc.route('/refresh')
@rpc.route("/refresh")
def rpc_refresh():
send_to_parent('refresh')
return jsonify(dict(status='ok'))
send_to_parent("refresh")
return jsonify(dict(status="ok"))
@rpc.route('/audiospec', methods=['GET'])
@rpc.route("/audiospec", methods=["GET"])
def get_audiospec():
return jsonify(current_app.larigira.controller.player.continous_audiospec)
@rpc.route('/audiospec', methods=['PUT'])
@rpc.route("/audiospec", methods=["PUT"])
def change_audiospec():
player = current_app.larigira.controller.player
if request.json is None:
abort(400, "Must send application/json data")
if 'spec' not in request.json or type(request.json['spec']) is not dict:
if "spec" not in request.json or type(request.json["spec"]) is not dict:
abort(400, "Object must have a key 'spec' whose value is an object")
player.continous_audiospec = request.json['spec']
if 'kind' not in request.json['spec']:
player.continous_audiospec = request.json["spec"]
if "kind" not in request.json["spec"]:
abort(400, "invalid audiospec")
return jsonify(player.continous_audiospec)
@rpc.route('/audiospec', methods=['DELETE'])
@rpc.route("/audiospec", methods=["DELETE"])
def reset_audiospec():
player = current_app.larigira.controller.player
player.continous_audiospec = None
return jsonify(player.continous_audiospec)
@rpc.route('/eventsenabled/toggle', methods=['POST'])
@rpc.route("/eventsenabled/toggle", methods=["POST"])
def toggle_events_enabled():
status = current_app.larigira.controller.player.events_enabled
current_app.larigira.controller.player.events_enabled = not status
return jsonify(dict(events_enabled=not status))
@rpc.route('/eventsenabled', methods=['GET'])
@rpc.route("/eventsenabled", methods=["GET"])
def get_events_enabled():
status = current_app.larigira.controller.player.events_enabled
return jsonify(dict(events_enabled=status))
@rpc.route('/eventsenabled', methods=['PUT'])
@rpc.route("/eventsenabled", methods=["PUT"])
def set_events_enabled():
player = current_app.larigira.controller.player
if request.json is None:
@ -102,45 +111,42 @@ def get_scheduled_audiogen():
for timespec_eid in events:
orig_info = running[timespec_eid]
info = events[timespec_eid]
info['running_time'] = orig_info['running_time'].isoformat()
info['audiospecs'] = orig_info['audiospecs']
info['timespec'] = orig_info['timespec']
info['timespec']['actions'] = {aid: spec
for aid, spec
in zip(info['timespec']['actions'],
info['audiospecs'])
}
info['greenlet'] = hex(id(orig_info['greenlet']))
info["running_time"] = orig_info["running_time"].isoformat()
info["audiospecs"] = orig_info["audiospecs"]
info["timespec"] = orig_info["timespec"]
info["timespec"]["actions"] = {
aid: spec
for aid, spec in zip(info["timespec"]["actions"], info["audiospecs"])
}
info["greenlet"] = hex(id(orig_info["greenlet"]))
return events
@viewui.route('/status/running')
@viewui.route("/status/running")
def ui_wip():
audiogens = get_scheduled_audiogen()
return render_template('running.html',
audiogens=sorted(
audiogens.items(),
key=lambda x: x[1]['running_time'])
)
return render_template(
"running.html",
audiogens=sorted(audiogens.items(), key=lambda x: x[1]["running_time"]),
)
@rpc.route('/debug/running')
@rpc.route("/debug/running")
def rpc_wip():
def treeify(flat):
roots = [obid for obid in flat if flat[obid]['parent'] not in flat]
roots = [obid for obid in flat if flat[obid]["parent"] not in flat]
tree = deepcopy(flat)
for obid in tree:
tree[obid]['children'] = {}
tree[obid]["children"] = {}
to_remove = []
for obid in tree:
if obid in roots:
continue
if obid not in tree:
current_app.logger.warning('How strange, {} not in tree'
.format(obid))
current_app.logger.warning("How strange, {} not in tree".format(obid))
continue
tree[tree[obid]['parent']]['children'][obid] = tree[obid]
tree[tree[obid]["parent"]]["children"][obid] = tree[obid]
to_remove.append(obid)
for obid in to_remove:
@ -148,30 +154,28 @@ def rpc_wip():
return tree
greenlets = {}
for ob in filter(lambda obj: isinstance(obj, greenlet),
gc.get_objects()):
objrepr = {
'repr': repr(ob),
'class': ob.__class__.__name__,
}
if hasattr(ob, 'parent_greenlet') and ob.parent_greenlet is not None:
objrepr['parent'] = hex(id(ob.parent_greenlet))
for ob in filter(lambda obj: isinstance(obj, greenlet), gc.get_objects()):
objrepr = {"repr": repr(ob), "class": ob.__class__.__name__}
if hasattr(ob, "parent_greenlet") and ob.parent_greenlet is not None:
objrepr["parent"] = hex(id(ob.parent_greenlet))
else:
objrepr['parent'] = hex(id(ob.parent)) \
if ob.parent is not None else None
if hasattr(ob, 'doc'):
objrepr['doc'] = ob.doc.split('\n')[0]
objrepr["parent"] = hex(id(ob.parent)) if ob.parent is not None else None
if hasattr(ob, "doc"):
objrepr["doc"] = ob.doc.split("\n")[0]
elif ob.__doc__:
objrepr['doc'] = ob.__doc__.split('\n')[0]
objrepr["doc"] = ob.__doc__.split("\n")[0]
greenlets[hex(id(ob))] = objrepr
# TODO: make it a tree
return jsonify(dict(greenlets=greenlets,
greenlets_tree=treeify(greenlets),
audiogens=get_scheduled_audiogen(),
))
return jsonify(
dict(
greenlets=greenlets,
greenlets_tree=treeify(greenlets),
audiogens=get_scheduled_audiogen(),
)
)
def babel_get_locale():
@ -183,8 +187,7 @@ def babel_get_locale():
def create_app(queue, larigira):
app = Flask('larigira',
static_url_path=get_conf()['ROUTE_PREFIX'] + '/static')
app = Flask("larigira", static_url_path=get_conf()["ROUTE_PREFIX"] + "/static")
app.config.update(get_conf())
Bootstrap(app)
babel = Babel(app)
@ -192,6 +195,9 @@ def create_app(queue, larigira):
app.register_blueprint(rpc)
app.register_blueprint(viewui)
app.register_blueprint(db)
app.route("/")(
lambda: redirect(get_conf()["ROUTE_PREFIX"] + get_conf()["HOME_URL"])
)
app.queue = queue
app.larigira = larigira
app.cache = SimpleCache()