#!/usr/bin/env python import os from .__init__ import DATA_DIR os.chdir(DATA_DIR) # server stuff from bottle import Bottle, route, get, post, error, run, template, static_file, request, response, FormsDict, redirect, template, HTTPResponse, BaseRequest import waitress # monkey patching from . import monkey # rest of the project from . import database from . import htmlmodules from . import htmlgenerators from . import malojatime from . import utilities from .utilities import resolveImage from .urihandler import uri_to_internal, remove_identical from . import urihandler # doreah toolkit from doreah import settings from doreah.logging import log from doreah.timing import Clock from doreah.pyhp import file as pyhpfile # technical #from importlib.machinery import SourceFileLoader import importlib import _thread import sys import signal import os import setproctitle import pkg_resources # url handling import urllib #settings.config(files=["settings/default.ini","settings/settings.ini"]) #settings.update("settings/default.ini","settings/settings.ini") MAIN_PORT = settings.get_settings("WEB_PORT") HOST = settings.get_settings("HOST") THREADS = 12 BaseRequest.MEMFILE_MAX = 15 * 1024 * 1024 WEBFOLDER = pkg_resources.resource_filename(__name__,"web") STATICFOLDER = pkg_resources.resource_filename(__name__,"static") webserver = Bottle() pthjoin = os.path.join def generate_css(): import lesscpy from io import StringIO less = "" for f in os.listdir(pthjoin(STATICFOLDER,"less")): with open(pthjoin(STATICFOLDER,"less",f),"r") as lessf: less += lessf.read() css = lesscpy.compile(StringIO(less),minify=True) return css css = generate_css() #os.makedirs("web/css",exist_ok=True) #with open("web/css/style.css","w") as f: # f.write(css) @webserver.route("") @webserver.route("/") def mainpage(): response = static_html("start") return response @webserver.error(400) @webserver.error(403) @webserver.error(404) @webserver.error(405) @webserver.error(408) @webserver.error(500) @webserver.error(505) def customerror(error): code = int(str(error).split(",")[0][1:]) log("HTTP Error: " + str(code),module="error") if os.path.exists(pthjoin(WEBFOLDER,"errors",str(code) + ".pyhp")): return pyhpfile(pthjoin(WEBFOLDER,"errors",str(code) + ".pyhp"),{"errorcode":code}) else: return pyhpfile(pthjoin(WEBFOLDER,"errors","generic.pyhp"),{"errorcode":code}) def graceful_exit(sig=None,frame=None): #urllib.request.urlopen("http://[::1]:" + str(DATABASE_PORT) + "/sync") log("Received signal to shutdown") try: database.sync() except Exception as e: log("Error while shutting down!",e) 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/") @webserver.route("/images/") @webserver.route("/images/") @webserver.route("/images/") def static_image(pth): small_pth = pth + "-small" if os.path.exists("images/" + small_pth): response = static_file("images/" + small_pth,root="") else: try: from wand.image import Image img = Image(filename="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="images/" + small_pth) response = static_file("images/" + small_pth,root="") else: response = static_file("images/" + pth,root="") except: response = static_file("images/" + pth,root="") #response = static_file("images/" + pth,root="") response.set_header("Cache-Control", "public, max-age=86400") return response @webserver.route("/style.css") def get_css(): response.content_type = 'text/css' return css @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 @webserver.route("/") def static_html(name): linkheaders = ["; rel=preload; as=style"] keys = remove_identical(FormsDict.decode(request.query)) pyhp_file = os.path.exists(pthjoin(WEBFOLDER,name + ".pyhp")) html_file = os.path.exists(pthjoin(WEBFOLDER,name + ".html")) pyhp_pref = settings.get_settings("USE_PYHP") adminmode = request.cookies.get("adminmode") == "true" and database.checkAPIkey(request.cookies.get("apikey")) is not False clock = Clock() clock.start() # if a pyhp file exists, use this if (pyhp_file and pyhp_pref) or (pyhp_file and not html_file): environ = {} #things we expose to the pyhp pages environ["adminmode"] = adminmode if adminmode: environ["apikey"] = request.cookies.get("apikey") # maloja environ["db"] = database environ["htmlmodules"] = htmlmodules environ["htmlgenerators"] = htmlgenerators environ["malojatime"] = malojatime environ["utilities"] = utilities environ["urihandler"] = urihandler #environ["info"] = info # external environ["urllib"] = urllib # request environ["filterkeys"], environ["limitkeys"], environ["delimitkeys"], environ["amountkeys"] = uri_to_internal(keys) #response.set_header("Content-Type","application/xhtml+xml") res = pyhpfile(pthjoin(WEBFOLDER,name + ".pyhp"),environ) log("Generated page " + name + " in " + str(clock.stop()) + "s (PYHP)",module="debug") return res # if not, use the old way else: with open(pthjoin(WEBFOLDER,name + ".html")) as htmlfile: html = htmlfile.read() # apply global substitutions with open(pthjoin(WEBFOLDER,"common/footer.html")) as footerfile: footerhtml = footerfile.read() with open(pthjoin(WEBFOLDER,"common/header.html")) as headerfile: headerhtml = headerfile.read() html = html.replace("",footerhtml + "").replace("",headerhtml + "") # If a python file exists, it provides the replacement dict for the html file if os.path.exists(pthjoin(WEBFOLDER,name + ".py")): #txt_keys = SourceFileLoader(name,"web/" + name + ".py").load_module().replacedict(keys,DATABASE_PORT) try: module = importlib.import_module(".web." + name,package="maloja") txt_keys,resources = module.instructions(keys) except Exception as e: log("Error in website generation: " + str(sys.exc_info()),module="error") raise # add headers for server push for resource in resources: if all(ord(c) < 128 for c in resource["file"]): # we can only put ascii stuff in the http header linkheaders.append("<" + resource["file"] + ">; rel=preload; as=" + resource["type"]) # apply key substitutions for k in txt_keys: if isinstance(txt_keys[k],list): # if list, we replace each occurence with the next item for element in txt_keys[k]: html = html.replace(k,element,1) else: html = html.replace(k,txt_keys[k]) response.set_header("Link",",".join(linkheaders)) log("Generated page " + name + " in " + str(clock.stop()) + "s (Python+HTML)",module="debug") return html #return static_file("web/" + name + ".html",root="") # 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("/"))) #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") ## start database database.start_db() database.dbserver.mount(server=webserver) log("Starting up Maloja server...") #run(webserver, host=HOST, port=MAIN_PORT, server='waitress') waitress.serve(webserver, host=HOST, port=MAIN_PORT, threads=THREADS)