Merge pull request #9 from pflarr/pflarr_edits

New verbose logging and file service
This commit is contained in:
Neale Pickett 2016-10-19 15:33:18 -06:00 committed by GitHub
commit e7198d6ac8
2 changed files with 102 additions and 22 deletions

View File

@ -102,35 +102,92 @@ you are a fool.
body = [] body = []
path = self.path.rstrip('/') path = self.path.rstrip('/')
parts = path.split("/") parts = path.split("/")
#raise ValueError(parts)
if len(parts) < 3: if len(parts) < 3:
# List all categories # List all categories
body.append("# Puzzle Categories") body.append("# Puzzle Categories")
for i in glob.glob(os.path.join("puzzles", "*", "")): for i in glob.glob(os.path.join("puzzles", "*", "")):
body.append("* [{}](/{})".format(i, i)) 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 # List all point values in a category
body.append("# Puzzles in category `{}`".format(parts[2])) 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: for points in cat.pointvals:
body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=points)) 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])) body.append("# {} puzzle {}".format(parts[2], parts[3]))
fpath = os.path.join("puzzles", parts[2]) body.append("* Author: `{}`".format(pzl['author']))
cat = puzzles.Category(fpath, seed) body.append("* Summary: `{}`".format(pzl['summary']))
p = cat.puzzle(int(parts[3]))
body.append("* Author: `{}`".format(p['author']))
body.append("* Summary: `{}`".format(p['summary']))
body.append('') body.append('')
body.append("## Body") body.append("## Body")
body.append(p.body) body.append(pzl.body)
body.append("## Answers") body.append("## Answers")
for a in p['answers']: for a in pzl['answer']:
body.append("* `{}`".format(a)) body.append("* `{}`".format(a))
body.append("") 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))
if len(pzl.logs) > 0:
body.extend(["", "## Logs"])
body.append("* [Full Log File](_logs)"
.format(cat=parts[2], points=pzl.points))
body.extend(["", "### Logs Head"])
for log in pzl.logs[:10]:
body.append("* `{}`".format(log))
body.extend(["", "### Logs Tail"])
for log in pzl.logs[-10:]:
body.append("* `{}`".format(log))
self.serve_md('\n'.join(body))
return
elif len(parts) == 5:
if parts[4] == '_logs':
self.serve_puzzle_logs(pzl.logs)
else:
try:
self.serve_puzzle_file(pzl['files'][parts[4]])
except KeyError:
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
return
else: else:
body.append("# Not Implemented Yet") body.append("# Not Implemented Yet")
self.serve_md('\n'.join(body)) self.serve_md('\n'.join(body))
def serve_puzzle_logs(self, logs):
"""Serve a PuzzleFile object."""
self.send_response(HTTPStatus.OK)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.end_headers()
for log in logs:
self.wfile.write(log.encode('ascii'))
self.wfile.write(b"\n")
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): def serve_file(self):
if self.path.endswith(".md"): if self.path.endswith(".md"):

View File

@ -88,12 +88,15 @@ class Puzzle:
self._dict = defaultdict(lambda: []) self._dict = defaultdict(lambda: [])
if os.path.isdir(path): if os.path.isdir(path):
self._puzzle_dir = path self.puzzle_dir = path
else: else:
self._puzzle_dir = None self.puzzle_dir = None
self.message = bytes(random.choice(messageChars) for i in range(20)) self.message = bytes(random.choice(messageChars) for i in range(20))
self.body = '' 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. # A list of temporary files we've created that will need to be deleted.
self._temp_files = [] self._temp_files = []
if path is not None: if path is not None:
@ -111,6 +114,8 @@ class Puzzle:
self._seed = category_seed * self.points self._seed = category_seed * self.points
self.rand = random.Random(self._seed) self.rand = random.Random(self._seed)
self._logs = []
if path is not None: if path is not None:
files = os.listdir(path) files = os.listdir(path)
@ -136,6 +141,25 @@ class Puzzle:
except OSError: except OSError:
pass pass
def log(self, msg):
"""Add a new log message to this puzzle."""
self._logs.append(msg)
@property
def logs(self):
"""Get all the log messages, as strings."""
_logs = []
for log in self._logs:
if type(log) is bytes:
log = log.decode('utf-8')
elif type(log) is not str:
log = str(log)
_logs.append(log)
return _logs
def _read_config(self, stream): def _read_config(self, stream):
"""Read a configuration file (ISO 2822)""" """Read a configuration file (ISO 2822)"""
body = [] body = []
@ -171,18 +195,17 @@ class Puzzle:
# Make sure it actually exists. # Make sure it actually exists.
if not os.path.exists(path): 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') file = open(path, 'rb')
return PuzzleFile(path=path, handle=file, name=name, visible=visible) 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 """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. 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 :param name: The name of the file for links within the puzzle. If this is None, a name
will be generated for you. will be generated for you.
:param mode: The mode under which
:param visible: Whether or not the file will be visible to the user. :param visible: Whether or not the file will be visible to the user.
:return: A file object for writing :return: A file object for writing
""" """
@ -190,7 +213,7 @@ class Puzzle:
if name is None: if name is None:
name = self.random_hash() 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') file_read = open(file.name, 'rb')
self._dict['files'][name] = PuzzleFile(path=file.name, handle=file_read, self._dict['files'][name] = PuzzleFile(path=file.name, handle=file_read,
@ -218,7 +241,7 @@ class Puzzle:
key = key.lower() key = key.lower()
if key in ('file', 'resource', 'hidden') and self._puzzle_dir is None: if key in ('file', 'resource', 'hidden') and self.puzzle_dir is None:
raise KeyError("Cannot set a puzzle file for single file puzzles.") raise KeyError("Cannot set a puzzle file for single file puzzles.")
if key == 'answer': if key == 'answer':
@ -227,15 +250,15 @@ class Puzzle:
self._dict['answer'].append(value) self._dict['answer'].append(value)
elif key == 'file': elif key == 'file':
# Handle adding files to the puzzle # Handle adding files to the puzzle
path = os.path.join(self._puzzle_dir, 'files', value) path = os.path.join(self.puzzle_dir, 'files', value)
self._dict['files'][value] = self._puzzle_file(path, value) self._dict['files'][value] = self._puzzle_file(path, value)
elif key == 'resource': elif key == 'resource':
# Handle adding category files to the puzzle # Handle adding category files to the puzzle
path = os.path.join(self._puzzle_dir, '../res', value) path = os.path.join(self.puzzle_dir, '../res', value)
self._dict['files'].append(self._puzzle_file(path, value)) self._dict['files'].append(self._puzzle_file(path, value))
elif key == 'hidden': elif key == 'hidden':
# Handle adding secret, 'hidden' files to the puzzle. # Handle adding secret, 'hidden' files to the puzzle.
path = os.path.join(self._puzzle_dir, 'files', value) path = os.path.join(self.puzzle_dir, 'files', value)
name = self.random_hash() name = self.random_hash()
self._dict['files'].append(self._puzzle_file(path, name, visible=False)) self._dict['files'].append(self._puzzle_file(path, name, visible=False))
elif key in self.SINGULAR_KEYS: elif key in self.SINGULAR_KEYS: