From b4230c0ae6facb56ca4a64172f234504eb6882cc Mon Sep 17 00:00:00 2001 From: krateng Date: Thu, 9 Dec 2021 05:49:25 +0100 Subject: [PATCH] Reorganized main loop and added temporary endpoints during DB build, GH-88 --- maloja/server.py | 401 +++++++++++++++++++++++++++++------------------ 1 file changed, 249 insertions(+), 152 deletions(-) diff --git a/maloja/server.py b/maloja/server.py index 241b1ba..97a4abf 100644 --- a/maloja/server.py +++ b/maloja/server.py @@ -27,7 +27,7 @@ from doreah import auth # technical #from importlib.machinery import SourceFileLoader import importlib -import _thread +from threading import Thread import sys import signal import os @@ -39,6 +39,11 @@ from css_html_js_minify import html_minify, css_minify import urllib +###### +### TECHNICAL SETTINGS +##### + + #settings.config(files=["settings/default.ini","settings/settings.ini"]) #settings.update("settings/default.ini","settings/settings.ini") MAIN_PORT = settings.get_settings("WEB_PORT") @@ -49,24 +54,14 @@ BaseRequest.MEMFILE_MAX = 15 * 1024 * 1024 STATICFOLDER = pkg_resources.resource_filename(__name__,"web/static") webserver = Bottle() -auth.authapi.mount(server=webserver) - -from .apis import init_apis -init_apis(webserver) - -# redirects for backwards compatibility -@webserver.get("/api/s/") -@webserver.post("/api/s/") -def deprecated_api_s(pth): - redirect("/apis/" + pth + "?" + request.query_string,308) - -@webserver.get("/api/") -@webserver.post("/api/") -def deprecated_api(pth): - redirect("/apis/mlj_1/" + pth + "?" + request.query_string,308) +#rename process, this is now required for the daemon manager to work +setproctitle.setproctitle("Maloja") +###### +### CSS +##### def generate_css(): @@ -81,16 +76,37 @@ def generate_css(): css = generate_css() + +###### +### MINIFY +##### + def clean_html(inp): if settings.get_settings("DEV_MODE"): return inp else: return html_minify(inp) -@webserver.route("") -@webserver.route("/") -def mainpage(): - return static_html("start") + + + + + + + + + + + + + + + + +###### +### ERRORS +##### + @webserver.error(400) @webserver.error(403) @@ -98,11 +114,12 @@ def mainpage(): @webserver.error(405) @webserver.error(408) @webserver.error(500) +@webserver.error(503) @webserver.error(505) def customerror(error): errorcode = error.status_code errordesc = error.status - traceback = error.traceback + traceback = error.traceback or error.body traceback = traceback.strip() if traceback is not None else "No Traceback" adminmode = request.cookies.get("adminmode") == "true" and auth.check(request) @@ -116,6 +133,179 @@ def customerror(error): + + + + + + + +###### +### REGISTERING ENDPOINTS +##### + +aliases = { + "admin": "admin_overview", + "manual": "admin_manual", + "setup": "admin_setup", + "issues": "admin_issues" +} + + +def register_endpoints_api(): + auth.authapi.mount(server=webserver) + + from .apis import init_apis + init_apis(webserver) + + # redirects for backwards compatibility + @webserver.get("/api/s/") + @webserver.post("/api/s/") + def deprecated_api_s(pth): + redirect("/apis/" + pth + "?" + request.query_string,308) + + @webserver.get("/api/") + @webserver.post("/api/") + def deprecated_api(pth): + redirect("/apis/mlj_1/" + pth + "?" + request.query_string,308) + +def register_endpoints_web_static(): + + @webserver.route("/image") + def dynamic_image(): + keys = FormsDict.decode(request.query) + relevant, _, _, _, _ = uri_to_internal(keys) + result = resolveImage(**relevant) + if result == "": return "" + redirect(result,307) + + @webserver.route("/images/") + @webserver.route("/images/") + @webserver.route("/images/") + @webserver.route("/images/") + def static_image(pth): + if globalconf.USE_THUMBOR: + return static_file(pth,root=data_dir['images']()) + + type = pth.split(".")[-1] + small_pth = pth + "-small" + if os.path.exists(data_dir['images'](small_pth)): + response = static_file(small_pth,root=data_dir['images']()) + else: + try: + from wand.image import Image + img = Image(filename=data_dir['images'](pth)) + x,y = img.size[0], img.size[1] + smaller = min(x,y) + if smaller > 300: + ratio = 300/smaller + img.resize(int(ratio*x),int(ratio*y)) + img.save(filename=data_dir['images'](small_pth)) + response = static_file(small_pth,root=data_dir['images']()) + else: + response = static_file(pth,root=data_dir['images']()) + except: + response = static_file(pth,root=data_dir['images']()) + + #response = static_file("images/" + pth,root="") + response.set_header("Cache-Control", "public, max-age=86400") + response.set_header("Content-Type", "image/" + type) + return response + + + @webserver.route("/style.css") + def get_css(): + response.content_type = 'text/css' + global css + if settings.get_settings("DEV_MODE"): css = generate_css() + return css + + + @webserver.route("/login") + def login(): + return auth.get_login_page() + + @webserver.route("/.") + def static(name,ext): + assert ext in ["txt","ico","jpeg","jpg","png","less","js"] + response = static_file(ext + "/" + name + "." + ext,root=STATICFOLDER) + response.set_header("Cache-Control", "public, max-age=3600") + return response + + @webserver.route("/media/.") + def static(name,ext): + assert ext in ["ico","jpeg","jpg","png"] + response = static_file(ext + "/" + name + "." + ext,root=STATICFOLDER) + response.set_header("Cache-Control", "public, max-age=3600") + return response + + + + + + + +def register_endpoints_web_dynamic(): + + def static_html(name): + if name in aliases: redirect(aliases[name]) + linkheaders = ["; rel=preload; as=style"] + keys = remove_identical(FormsDict.decode(request.query)) + + adminmode = request.cookies.get("adminmode") == "true" and auth.check(request) + + clock = Clock() + clock.start() + + LOCAL_CONTEXT = { + "adminmode":adminmode, + "apikey":request.cookies.get("apikey") if adminmode else None, + "_urikeys":keys, #temporary! + } + lc = LOCAL_CONTEXT + lc["filterkeys"], lc["limitkeys"], lc["delimitkeys"], lc["amountkeys"], lc["specialkeys"] = uri_to_internal(keys) + + template = jinja_environment.get_template(name + '.jinja') + try: + res = template.render(**LOCAL_CONTEXT) + except ValueError as e: + abort(404,"Entity does not exist") + + if settings.get_settings("DEV_MODE"): jinja_environment.cache.clear() + + log("Generated page {name} in {time:.5f}s".format(name=name,time=clock.stop()),module="debug_performance") + return clean_html(res) + + @webserver.route("/") + @auth.authenticated + def static_html_private(name): + return static_html(name) + + @webserver.route("/") + def static_html_public(name): + return static_html(name) + + @webserver.route("") + @webserver.route("/") + def mainpage(): + return static_html("start") + + + # Shortlinks + + @webserver.get("/artist/") + def redirect_artist(artist): + redirect("/artist?artist=" + artist) + @webserver.get("/track//") + def redirect_track(artists,title): + redirect("/track?title=" + title + "&" + "&".join("artist=" + artist for artist in artists.split("/"))) + + +###### +### SHUTDOWN +##### + + def graceful_exit(sig=None,frame=None): #urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/sync") log("Received signal to shutdown") @@ -126,144 +316,49 @@ def graceful_exit(sig=None,frame=None): log("Server shutting down...") os._exit(42) - -@webserver.route("/image") -def dynamic_image(): - keys = FormsDict.decode(request.query) - relevant, _, _, _, _ = uri_to_internal(keys) - result = resolveImage(**relevant) - if result == "": return "" - redirect(result,307) - -@webserver.route("/images/<pth:re:.*\\.jpeg>") -@webserver.route("/images/<pth:re:.*\\.jpg>") -@webserver.route("/images/<pth:re:.*\\.png>") -@webserver.route("/images/<pth:re:.*\\.gif>") -def static_image(pth): - if globalconf.USE_THUMBOR: - return static_file(pth,root=data_dir['images']()) - - type = pth.split(".")[-1] - small_pth = pth + "-small" - if os.path.exists(data_dir['images'](small_pth)): - response = static_file(small_pth,root=data_dir['images']()) - else: - try: - from wand.image import Image - img = Image(filename=data_dir['images'](pth)) - x,y = img.size[0], img.size[1] - smaller = min(x,y) - if smaller > 300: - ratio = 300/smaller - img.resize(int(ratio*x),int(ratio*y)) - img.save(filename=data_dir['images'](small_pth)) - response = static_file(small_pth,root=data_dir['images']()) - else: - response = static_file(pth,root=data_dir['images']()) - except: - response = static_file(pth,root=data_dir['images']()) - - #response = static_file("images/" + pth,root="") - response.set_header("Cache-Control", "public, max-age=86400") - response.set_header("Content-Type", "image/" + type) - return response - - -@webserver.route("/style.css") -def get_css(): - response.content_type = 'text/css' - global css - if settings.get_settings("DEV_MODE"): css = generate_css() - return css - - -@webserver.route("/login") -def login(): - return auth.get_login_page() - -@webserver.route("/<name>.<ext>") -def static(name,ext): - assert ext in ["txt","ico","jpeg","jpg","png","less","js"] - response = static_file(ext + "/" + name + "." + ext,root=STATICFOLDER) - response.set_header("Cache-Control", "public, max-age=3600") - return response - -@webserver.route("/media/<name>.<ext>") -def static(name,ext): - assert ext in ["ico","jpeg","jpg","png"] - response = static_file(ext + "/" + name + "." + ext,root=STATICFOLDER) - response.set_header("Cache-Control", "public, max-age=3600") - return response - - -aliases = { - "admin": "admin_overview", - "manual": "admin_manual", - "setup": "admin_setup", - "issues": "admin_issues" -} - - - - -@webserver.route("/<name:re:admin.*>") -@auth.authenticated -def static_html_private(name): - return static_html(name) - -@webserver.route("/<name>") -def static_html_public(name): - return static_html(name) - -def static_html(name): - if name in aliases: redirect(aliases[name]) - linkheaders = ["</style.css>; rel=preload; as=style"] - keys = remove_identical(FormsDict.decode(request.query)) - - adminmode = request.cookies.get("adminmode") == "true" and auth.check(request) - - clock = Clock() - clock.start() - - LOCAL_CONTEXT = { - "adminmode":adminmode, - "apikey":request.cookies.get("apikey") if adminmode else None, - "_urikeys":keys, #temporary! - } - lc = LOCAL_CONTEXT - lc["filterkeys"], lc["limitkeys"], lc["delimitkeys"], lc["amountkeys"], lc["specialkeys"] = uri_to_internal(keys) - - template = jinja_environment.get_template(name + '.jinja') - try: - res = template.render(**LOCAL_CONTEXT) - except ValueError as e: - abort(404,"Entity does not exist") - - if settings.get_settings("DEV_MODE"): jinja_environment.cache.clear() - - log("Generated page {name} in {time:.5f}s".format(name=name,time=clock.stop()),module="debug_performance") - return clean_html(res) - - -# Shortlinks - -@webserver.get("/artist/<artist>") -def redirect_artist(artist): - redirect("/artist?artist=" + artist) -@webserver.get("/track/<artists:path>/<title>") -def redirect_track(artists,title): - redirect("/track?title=" + title + "&" + "&".join("artist=" + artist for artist in artists.split("/"))) - #set graceful shutdown signal.signal(signal.SIGINT, graceful_exit) signal.signal(signal.SIGTERM, graceful_exit) -#rename process, this is now required for the daemon manager to work -setproctitle.setproctitle("Maloja") + + + + +###### +### RUNNING THE SERVER +##### + + + +def register_temp_endpoint(): + + @webserver.route("/<path:path>") + @webserver.route("") + @webserver.route("/") + def notyet(*args,**kwargs): + abort(503,"Database is still being built") + +def unregister_temp_endpoint(): + pass + # TODO + +def wait_for_db(): + + register_endpoints_web_static() + register_temp_endpoint() + + database.start_db() + + register_endpoints_api() + register_endpoints_web_dynamic() + + unregister_temp_endpoint() def run_server(): + + Thread(target=wait_for_db).start() ## start database - database.start_db() + log("Starting up Maloja server...") @@ -274,4 +369,6 @@ def run_server(): log("Error. Is another Maloja process already running?") raise + + run_server()