diff --git a/devel-server.py b/devel-server.py index 5543f83..0f15dfd 100755 --- a/devel-server.py +++ b/devel-server.py @@ -102,35 +102,69 @@ you are a fool. body = [] path = self.path.rstrip('/') parts = path.split("/") + #raise ValueError(parts) if len(parts) < 3: # List all categories body.append("# Puzzle Categories") for i in glob.glob(os.path.join("puzzles", "*", "")): body.append("* [{}](/{})".format(i, i)) - elif len(parts) == 3: + self.serve_md('\n'.join(body)) + return + + fpath = os.path.join("puzzles", parts[2]) + cat = puzzles.Category(fpath, seed) + if len(parts) == 3: # List all point values in a category body.append("# Puzzles in category `{}`".format(parts[2])) - fpath = os.path.join("puzzles", parts[2]) - cat = puzzles.Category(fpath, seed) for points in cat.pointvals: body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=points)) - elif len(parts) == 4: + self.serve_md('\n'.join(body)) + return + + pzl = cat.puzzle(int(parts[3])) + if len(parts) == 4: body.append("# {} puzzle {}".format(parts[2], parts[3])) - fpath = os.path.join("puzzles", parts[2]) - cat = puzzles.Category(fpath, seed) - p = cat.puzzle(int(parts[3])) - body.append("* Author: `{}`".format(p['author'])) - body.append("* Summary: `{}`".format(p['summary'])) + body.append("* Author: `{}`".format(pzl['author'])) + body.append("* Summary: `{}`".format(pzl['summary'])) body.append('') body.append("## Body") - body.append(p.body) + body.append(pzl.body) body.append("## Answers") - for a in p['answers']: + for a in pzl['answer']: body.append("* `{}`".format(a)) body.append("") + body.append("## Files") + for pzl_file in pzl['files']: + body.append("* [puzzles/{cat}/{points}/{filename}]({filename})" + .format(cat=parts[2], points=pzl.points, filename=pzl_file)) + self.serve_md('\n'.join(body)) + return + elif len(parts) == 5: + try: + self.serve_puzzle_file(pzl['files'][parts[4]]) + except KeyError: + self.send_error(HTTPStatus.NOT_FOUND, "File not found") + return else: body.append("# Not Implemented Yet") - self.serve_md('\n'.join(body)) + self.serve_md('\n'.join(body)) + + CHUNK_SIZE = 4096 + def serve_puzzle_file(self, file): + """Serve a PuzzleFile object.""" + self.send_response(HTTPStatus.OK) + self.send_header("Content-type", "application/octet-stream") + self.send_header('Content-Disposition', 'attachment; filename="{}"'.format(file.name)) + if file.path is not None: + fs = os.stat(file.path) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + + # We're using application/octet stream, so we can send the raw bytes. + self.end_headers() + chunk = file.handle.read(self.CHUNK_SIZE) + while chunk: + self.wfile.write(chunk) + chunk = file.handle.read(self.CHUNK_SIZE) def serve_file(self): if self.path.endswith(".md"): diff --git a/puzzles.py b/puzzles.py index 3923f89..df18c0c 100644 --- a/puzzles.py +++ b/puzzles.py @@ -94,6 +94,9 @@ class Puzzle: self.message = bytes(random.choice(messageChars) for i in range(20)) self.body = '' + # This defaults to a dict, not a list like most things + self._dict['files'] = {} + # A list of temporary files we've created that will need to be deleted. self._temp_files = [] if path is not None: @@ -171,18 +174,17 @@ class Puzzle: # Make sure it actually exists. if not os.path.exists(path): - raise ValueError("Included file {} does not exist.") + raise ValueError("Included file {} does not exist.".format(path)) file = open(path, 'rb') return PuzzleFile(path=path, handle=file, name=name, visible=visible) - def make_temp_file(self, name=None, mode='rw+b', visible=True): + def make_temp_file(self, name=None, visible=True): """Get a file object for adding dynamically generated data to the puzzle. When you're done with this file, flush it, but don't close it. :param name: The name of the file for links within the puzzle. If this is None, a name will be generated for you. - :param mode: The mode under which :param visible: Whether or not the file will be visible to the user. :return: A file object for writing """ @@ -190,7 +192,7 @@ class Puzzle: if name is None: name = self.random_hash() - file = tempfile.NamedTemporaryFile(mode=mode, delete=False) + file = tempfile.NamedTemporaryFile(mode='w+b', delete=False) file_read = open(file.name, 'rb') self._dict['files'][name] = PuzzleFile(path=file.name, handle=file_read, @@ -313,7 +315,7 @@ class Category: def puzzle(self, points): path = os.path.join(self.path, str(points)) - return Puzzle(path, self.seed) + return Puzzle(self.seed, path=path) def puzzles(self): for points in self.pointvals: