diff --git a/Dockerfile.moth-compile b/Dockerfile.moth-compile
index 5680e4f..cd1331f 100644
--- a/Dockerfile.moth-compile
+++ b/Dockerfile.moth-compile
@@ -2,6 +2,6 @@ FROM alpine
RUN apk --no-cache add python3 py3-pillow
-COPY tools/package-puzzles.py tools/moth.py tools/mistune.py tools/answer_words.txt /moth/
+COPY . /moth/
-ENTRYPOINT ["python3", "/moth/package-puzzles.py"]
+ENTRYPOINT ["python3", "/moth/tools/mothballer.py"]
diff --git a/tools/devel-server.py b/tools/devel-server.py
index 354f899..830eb9f 100755
--- a/tools/devel-server.py
+++ b/tools/devel-server.py
@@ -1,6 +1,7 @@
#!/usr/bin/python3
import asyncio
+import cgitb
import glob
import html
from aiohttp import web
@@ -15,11 +16,12 @@ import shutil
import socketserver
import sys
import traceback
+import mothballer
sys.dont_write_bytecode = True # Don't write .pyc files
def mkseed():
- return bytes(random.choice(b'abcdef0123456789') for i in range(40))
+ return bytes(random.choice(b'abcdef0123456789') for i in range(40)).decode('ascii')
class Page:
def __init__(self, title, depth=0):
@@ -72,11 +74,17 @@ async def handle_front(request):
return p.response(request)
async def handle_puzzlelist(request):
+ seed = request.query.get("seed", mkseed())
p = Page("Puzzle Categories", 1)
+ p.write("
seed = {}
".format(seed))
p.write("")
for i in sorted(glob.glob(os.path.join(request.app["puzzles_dir"], "*", ""))):
bn = os.path.basename(i.strip('/\\'))
- p.write('- puzzles/{}/
'.format(bn, bn))
+ p.write("- ")
+ p.write("[mb]".format(cat=bn, seed=seed))
+ p.write(" ")
+ p.write("{cat}".format(cat=bn, seed=seed))
+ p.write("
")
p.write("
")
return p.response(request)
@@ -137,7 +145,7 @@ async def handle_puzzle(request):
return p.response(request)
async def handle_puzzlefile(request):
- seed = request.query.get("seed", mkseed())
+ seed = request.query.get("seed", mkseed()).encode('ascii')
category = request.match_info.get("category")
points = int(request.match_info.get("points"))
filename = request.match_info.get("filename")
@@ -158,6 +166,25 @@ async def handle_puzzlefile(request):
resp.body = file.stream.read()
return resp
+async def handle_mothballer(request):
+ seed = request.query.get("seed", mkseed())
+ category = request.match_info.get("category")
+
+ try:
+ catdir = os.path.join(request.app["puzzles_dir"], category)
+ 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={}.zip".format(category)},
+ content_type="application/octet_stream",
+ )
+ return resp
if __name__ == '__main__':
import argparse
@@ -192,5 +219,6 @@ if __name__ == '__main__':
app.router.add_route("GET", "/puzzles/{category}/", handle_category)
app.router.add_route("GET", "/puzzles/{category}/{points}/", handle_puzzle)
app.router.add_route("GET", "/puzzles/{category}/{points}/{filename}", handle_puzzlefile)
+ app.router.add_route("GET", "/mothballer/{category}", handle_mothballer)
app.router.add_static("/files/", mydir, show_index=True)
web.run_app(app, host=addr, port=port)
diff --git a/tools/package-puzzles.py b/tools/mothballer.py
similarity index 91%
rename from tools/package-puzzles.py
rename to tools/mothballer.py
index 4a8da80..76832a5 100755
--- a/tools/package-puzzles.py
+++ b/tools/mothballer.py
@@ -89,15 +89,11 @@ def generate_html(ziphandle, puzzle, puzzledir, category, points, authors, files
ziphandle.writestr(os.path.join(puzzledir, 'index.html'), html_content.getvalue())
def build_category(categorydir, outdir):
- zipfileraw = tempfile.NamedTemporaryFile(delete=False)
- zf = zipfile.ZipFile(zipfileraw, 'x')
-
category_seed = binascii.b2a_hex(os.urandom(20))
puzzles_dict = {}
secrets = {}
categoryname = os.path.basename(categorydir.strip(os.sep))
- seedfn = os.path.join("category_seed.txt")
zipfilename = os.path.join(outdir, "%s.zip" % categoryname)
logging.info("Building {} from {}".format(zipfilename, categorydir))
@@ -111,16 +107,27 @@ def build_category(categorydir, outdir):
existing.close()
logging.debug("Using PRNG seed {}".format(category_seed))
- zf.writestr(seedfn, category_seed)
+ zipfileraw = tempfile.NamedTemporaryFile(delete=False)
+ mothball = package(categoryname, categorydir, zfraw)
+ shutil.copyfileobj(mothball, zipfileraw)
+ zipfileraw.close()
+ shutil.move(zipfileraw.name, zipfilename)
- cat = moth.Category(categorydir, category_seed)
+
+# Returns a file-like object containing the contents of the new zip file
+def package(categoryname, categorydir, seed):
+ zfraw = io.BytesIO()
+ zf = zipfile.ZipFile(zfraw, 'x')
+ zf.writestr("category_seed.txt", seed)
+
+ cat = moth.Category(categorydir, seed)
mapping = {}
answers = {}
summary = {}
for puzzle in cat:
logging.info("Processing point value {}".format(puzzle.points))
- hashmap = hashlib.sha1(category_seed)
+ hashmap = hashlib.sha1(seed.encode('utf-8'))
hashmap.update(str(puzzle.points).encode('utf-8'))
puzzlehash = hashmap.hexdigest()
@@ -152,8 +159,8 @@ def build_category(categorydir, outdir):
# clean up
zf.close()
-
- shutil.move(zipfileraw.name, zipfilename)
+ zfraw.seek(0)
+ return zfraw
if __name__ == '__main__':
diff --git a/www/res/style.css b/www/res/style.css
index c8ab4de..14f11e9 100644
--- a/www/res/style.css
+++ b/www/res/style.css
@@ -78,6 +78,16 @@ pre, tt {
height: 70%;
}
+.download {
+ background: #080;
+ color: white;
+ display: inline-block;
+}
+.download:link {
+ color: inherit;
+ text-decoration: inherit;
+}
+
h1 {
text-align: center;
font-size: 120%;
@@ -124,6 +134,6 @@ a:visited {
}
::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.2);
+ background: rgba(255, 255, 255, 0.2);
border-radius: 1em;
}