moth/devel/devel-server.py

191 lines
5.5 KiB
Python
Raw Normal View History

2017-10-30 11:25:58 -06:00
#!/usr/bin/python3
import asyncio
2018-09-28 12:15:38 -06:00
import cgitb
2016-10-22 10:35:55 -06:00
import html
from aiohttp import web
2016-10-22 10:35:55 -06:00
import io
2018-10-02 19:21:54 -06:00
import json
import mimetypes
import moth
import logging
2016-10-16 19:52:09 -06:00
import os
import pathlib
import random
2016-10-22 10:35:55 -06:00
import shutil
import socketserver
2016-10-18 09:34:06 -06:00
import sys
import traceback
2018-09-28 12:15:38 -06:00
import mothballer
sys.dont_write_bytecode = True # Don't write .pyc files
2018-10-10 09:26:44 -06:00
def get_seed(request):
seedstr = request.match_info.get("seed")
if seedstr == "random":
return random.getrandbits(32)
else:
return int(seedstr)
async def handle_puzzlelist(request):
2018-10-10 09:26:44 -06:00
seed = get_seed(request)
2018-10-02 19:21:54 -06:00
puzzles = {
2018-10-09 16:05:02 -06:00
"__devel__": [[0, ""]],
2018-10-02 19:21:54 -06:00
}
for p in request.app["puzzles_dir"].glob("*"):
if not p.is_dir() or p.match(".*"):
continue
catName = p.parts[-1]
cat = moth.Category(p, seed)
puzzles[catName] = [[i, str(i)] for i in cat.pointvals()]
puzzles[catName].append([0, ""])
if len(puzzles) <= 1:
logging.warning("No directories found matching {}/*".format(request.app["puzzles_dir"]))
2018-10-02 19:21:54 -06:00
return web.Response(
content_type="application/json",
body=json.dumps(puzzles),
)
async def handle_puzzle(request):
2018-10-10 09:26:44 -06:00
seed = get_seed(request)
category = request.match_info.get("category")
points = int(request.match_info.get("points"))
2018-10-02 19:21:54 -06:00
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
puzzle = cat.puzzle(points)
2018-10-02 19:21:54 -06:00
obj = puzzle.package()
obj["answers"] = puzzle.answers
obj["hint"] = puzzle.hint
obj["summary"] = puzzle.summary
2018-10-16 08:52:29 -06:00
obj["logs"] = puzzle.logs
2018-10-02 19:21:54 -06:00
return web.Response(
content_type="application/json",
body=json.dumps(obj),
)
async def handle_puzzlefile(request):
2018-10-10 09:26:44 -06:00
seed = get_seed(request)
category = request.match_info.get("category")
points = int(request.match_info.get("points"))
filename = request.match_info.get("filename")
2018-10-02 19:21:54 -06:00
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
puzzle = cat.puzzle(points)
try:
file = puzzle.files[filename]
except KeyError:
return web.Response(status=404)
2018-10-02 19:21:54 -06:00
content_type, _ = mimetypes.guess_type(file.name)
return web.Response(
body=file.stream.read(), # Is there no way to pipe this, must we slurp the whole thing into memory?
content_type=content_type,
)
2018-09-28 12:15:38 -06:00
async def handle_mothballer(request):
2018-10-10 09:26:44 -06:00
seed = get_seed(request)
2018-09-28 12:15:38 -06:00
category = request.match_info.get("category")
try:
2018-10-02 19:21:54 -06:00
catdir = request.app["puzzles_dir"].joinpath(category)
2018-09-28 12:15:38 -06:00
mb = mothballer.package(category, catdir, seed)
except:
body = cgitb.html(sys.exc_info())
resp = web.Response(text=body, content_type="text/html")
return resp
mb_buf = mb.read()
resp = web.Response(
body=mb_buf,
headers={"Content-Disposition": "attachment; filename={}.mb".format(category)},
content_type="application/octet_stream",
)
return resp
2018-10-02 19:21:54 -06:00
async def handle_index(request):
seed = random.getrandbits(32)
body = """<!DOCTYPE html>
<html>
<head><title>Dev Server</title></head>
<body>
<h1>Dev Server</h1>
<p>
2018-10-10 09:26:44 -06:00
You need to provide the contest seed in the URL.
If you don't have a contest seed in mind,
why not try <a href="{seed}/">{seed}</a>?
</p>
<p>
If you are chaotic,
you could even take your chances with a
<a href="random/">random seed</a> for every HTTP request.
This means generated files will get a different seed than the puzzle itself!
</p>
2018-10-02 19:21:54 -06:00
</body>
</html>
""".format(seed=seed)
return web.Response(
content_type="text/html",
body=body,
)
async def handle_static(request):
2018-10-09 16:05:02 -06:00
themes = request.app["theme_dir"]
2018-10-02 19:21:54 -06:00
fn = request.match_info.get("filename")
if not fn:
2018-10-09 16:05:02 -06:00
for fn in ("puzzle-list.html", "index.html"):
path = themes.joinpath(fn)
if path.exists():
break
else:
path = themes.joinpath(fn)
return web.FileResponse(path)
2018-10-02 19:21:54 -06:00
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description="MOTH puzzle development server")
parser.add_argument(
'--puzzles', default='puzzles',
help="Directory containing your puzzles"
)
2018-10-02 19:21:54 -06:00
parser.add_argument(
'--theme', default='theme',
help="Directory containing theme files")
parser.add_argument(
'--bind', default="127.0.0.1:8080",
help="Bind to ip:port"
)
parser.add_argument(
'--base', default="",
help="Base URL to this server, for reverse proxy setup"
)
args = parser.parse_args()
parts = args.bind.split(":")
addr = parts[0] or "0.0.0.0"
port = int(parts[1])
logging.basicConfig(level=logging.INFO)
mydir = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
app = web.Application()
app["base_url"] = args.base
2018-10-02 19:21:54 -06:00
app["puzzles_dir"] = pathlib.Path(args.puzzles)
app["theme_dir"] = pathlib.Path(args.theme)
app.router.add_route("GET", "/", handle_index)
app.router.add_route("GET", "/{seed}/puzzles.json", handle_puzzlelist)
app.router.add_route("GET", "/{seed}/content/{category}/{points}/puzzle.json", handle_puzzle)
app.router.add_route("GET", "/{seed}/content/{category}/{points}/{filename}", handle_puzzlefile)
app.router.add_route("GET", "/{seed}/mothballer/{category}", handle_mothballer)
app.router.add_route("GET", "/{seed}/{filename:.*}", handle_static)
web.run_app(app, host=addr, port=port)