Some form-printing code was using != instead of ==, which led to the date defaulting to today whenever it was specified.
361 lines
11 KiB
Python
Executable file
361 lines
11 KiB
Python
Executable file
#!/usr/bin/python
|
|
|
|
import cgi
|
|
import os
|
|
import sys
|
|
import time
|
|
import calendar
|
|
from datetime import datetime
|
|
import rlgalldb as rlgall
|
|
import cgitb
|
|
|
|
cgitb.enable()
|
|
|
|
infop = """<p>Looking for ttyrec files? Don't like digging through Web directory listings, converting time zones in your head, or guessing how many files
|
|
the game was spread across? The Archivist can find them all for you.</p>
|
|
"""
|
|
|
|
ttyrecbase = "/var/dgl/dgldir/ttyrec/"
|
|
|
|
def input_game(outf, selected=None):
|
|
"Prints the form components for selecting a game."
|
|
selstr = '<option label="{0}" value="{1}" selected="selected">{0}</option>\n'
|
|
unselstr = '<option label="{0}" value="{1}">{0}</option>\n'
|
|
outf.write('<div>Dungeon name: <select name="game">\n')
|
|
for game in rlgall.gamelist:
|
|
# This will cause trouble if someone puts games with identical names
|
|
# into rlgall.
|
|
if selected and selected == game.uname:
|
|
outf.write(selstr.format(game.name, game.uname))
|
|
else:
|
|
outf.write(unselstr.format(game.name, game.uname))
|
|
outf.write('</select></div>\n')
|
|
return
|
|
|
|
def input_name(outf, defaultval=None):
|
|
defstr = '<div>Adventurer\'s name: <input type="text" name="name" value="{0}"></div>\n'
|
|
if defaultval:
|
|
outf.write(defstr.format(defaultval))
|
|
else:
|
|
outf.write('<div>Adventurer\'s Name: <input type="text" name="name"></div>\n')
|
|
return
|
|
|
|
def input_time(outf, defaultval=None):
|
|
defstr = '<div>Time: <input type="text" name="time" value="{0}"></div>\n'
|
|
if defaultval:
|
|
outf.write(defstr.format(defaultval))
|
|
else:
|
|
outf.write('<div>Time: <input type="text" name="time"></div>\n')
|
|
return
|
|
|
|
def input_datetime(outf, dvals=[None, None, None, None, None, None]):
|
|
optstr = '<option value="{0}" label="{1}">{1}</option>\n'
|
|
soptstr = '<option value="{0}" label="{1}" selected="selected">{1}</option>\n'
|
|
tf = '<input type="text" size="2" maxlength="2" name="{0}" value="{1:02}">'
|
|
emptf = '<input type="text" size="2" maxlength="2" name="{0}">'
|
|
sstr = '<div>Date: <select name="year">\n'
|
|
# Default to today
|
|
now = datetime.now(rlgall.utc)
|
|
if dvals[0] == None:
|
|
dvals[0] = now.year
|
|
if dvals[1] == None:
|
|
dvals[1] = now.month
|
|
if dvals[2] == None:
|
|
dvals[2] = now.day
|
|
for year in range(2010, now.year + 1):
|
|
if year == dvals[0]:
|
|
sstr += soptstr.format(year, year)
|
|
else:
|
|
sstr += optstr.format(year, year)
|
|
sstr += '</select>\n'
|
|
outf.write(sstr)
|
|
sstr = '<select name="month">\n'
|
|
for month in range(1, 13):
|
|
if month == dvals[1]:
|
|
sstr += soptstr.format(month, calendar.month_name[month])
|
|
else:
|
|
sstr += optstr.format(month, calendar.month_name[month])
|
|
sstr += '</select>\n'
|
|
outf.write(sstr)
|
|
sstr = '<select name="day">\n'
|
|
for day in range(1, 32):
|
|
if day == dvals[2]:
|
|
sstr += soptstr.format(day, day)
|
|
else:
|
|
sstr += optstr.format(day, day)
|
|
sstr += '</select></div>\n'
|
|
outf.write(sstr)
|
|
sstr = '<div>Approximate time: '
|
|
if dvals[3] != None:
|
|
sstr += tf.format("hour", dvals[3])
|
|
else:
|
|
sstr += emptf.format("hour")
|
|
sstr += " : "
|
|
if dvals[4] != None:
|
|
sstr += tf.format("minute", dvals[4])
|
|
else:
|
|
sstr += emptf.format("minute")
|
|
sstr += " : "
|
|
if dvals[5] != None:
|
|
sstr += tf.format("second", dvals[5])
|
|
else:
|
|
sstr += emptf.format("second")
|
|
sstr += ' (optional)</div>\n'
|
|
outf.write(sstr)
|
|
return
|
|
|
|
def checkempty(cgidata):
|
|
exkeys = set(cgidata.keys())
|
|
wanted = set(["name", "game", "time", "year", "month", "day", "hour",
|
|
"minute", "second"])
|
|
return wanted.isdisjoint(exkeys)
|
|
|
|
def processname(fdata, errlist):
|
|
"Takes a CGI data object, extracts the player name, checks its validity, \
|
|
and returns it. If errors are encountered, they are appended to errlist."
|
|
cantfind = "I have no record of an adventurer called {0}."
|
|
if "name" not in fdata:
|
|
errlist.append("You didn\'t tell me the adventurer\'s name.")
|
|
return None
|
|
# Just in case someone supplies something nasty, make sure they can't go
|
|
# digging all over the filesystem.
|
|
formname = fdata.getfirst("name").rpartition("/")[2]
|
|
try:
|
|
os.stat(ttyrecbase + formname)
|
|
except OSError:
|
|
errlist.append(cantfind.format(cgi.escape(formname)))
|
|
return None
|
|
return formname
|
|
|
|
def processgame(fdata, errlist):
|
|
"Takes a CGI data object and returns the game from rlgall.gamelist that \
|
|
it asks for. Errors appended to errlist."
|
|
cantfind = "I've never heard of a dungeon called {0}."
|
|
if "game" not in fdata:
|
|
errlist.append('You didn\'t choose a dungeon.')
|
|
return None
|
|
formgame = fdata.getfirst("game")
|
|
for agame in rlgall.gamelist:
|
|
if agame.uname == formgame:
|
|
return agame
|
|
errlist.append(cantfind.format(cgi.escape(formgame)))
|
|
return None
|
|
|
|
def processtime(fdata, errlist, hlist):
|
|
"Takes a CGI data object and converts to a datetime object by finding \
|
|
fields called year, month, etc. Any errors get appended to errlist. \
|
|
hlist should contain 6 components, for ymd-hms fields."
|
|
|
|
# Timestamp overrides human-readable, even if it's invalid.
|
|
badtime = 'The time field is for Unix clocks, which never say anything \
|
|
like {0}. Try the form instead.'
|
|
utime = None
|
|
formtime = fdata.getfirst("time")
|
|
if formtime:
|
|
try:
|
|
utime = int(formtime)
|
|
except ValueError:
|
|
errlist.append(badtime.format(cgi.escape(formtime)))
|
|
return None
|
|
else:
|
|
if utime < 0:
|
|
utime = 0
|
|
if utime != None:
|
|
chtime = time.gmtime(utime)
|
|
for i in range(6):
|
|
hlist[i] = chtime[i]
|
|
return datetime.fromtimestamp(utime, rlgall.utc)
|
|
|
|
# Now try to get a human-readable specification.
|
|
lerrors = []
|
|
year = month = day = hour = minute = second = None
|
|
fyear = fdata.getfirst("year")
|
|
if not fyear:
|
|
lerrors.append("No year was provided.")
|
|
else:
|
|
try:
|
|
year = int(fyear)
|
|
except ValueError:
|
|
lerrors.append("Invalid year.")
|
|
else:
|
|
if year < 2010:
|
|
lerrors.append("The Gallery has only existed since 2010.")
|
|
elif year > time.gmtime()[0]:
|
|
lerrors.append("I can't see into the future.")
|
|
else:
|
|
hlist[0] = year # It is valid
|
|
fmonth = fdata.getfirst("month")
|
|
if not fmonth:
|
|
lerrors.append("No month was provided.")
|
|
else:
|
|
try:
|
|
month = int(fmonth)
|
|
except ValueError:
|
|
lerrors.append("Invalid month.")
|
|
else:
|
|
if month < 1 or month > 12:
|
|
lerrors.append("Invalid month.")
|
|
else:
|
|
hlist[1] = month
|
|
fday = fdata.getfirst("day")
|
|
if not fday:
|
|
lerrors.append("No day was provided.")
|
|
else:
|
|
try:
|
|
day = int(fday)
|
|
except ValueError:
|
|
lerrors.append("Invalid day.")
|
|
else:
|
|
if day < 1 or day > 31:
|
|
lerrors.append("Invalid day.")
|
|
elif not lerrors:
|
|
if day > calendar.monthrange(year, month)[1]:
|
|
lerrors.append("Invalid day.")
|
|
else:
|
|
hlist[2] = day
|
|
fhour = fdata.getfirst("hour")
|
|
if not fhour:
|
|
hour = 0 # Assume things.
|
|
hlist[3] = 0
|
|
else:
|
|
try:
|
|
hour = int(fhour)
|
|
except ValueError:
|
|
lerrors.append("Invalid hour.")
|
|
else:
|
|
if hour < 0 or hour > 23:
|
|
lerrors.append("Invalid hour.")
|
|
else:
|
|
hlist[3] = hour
|
|
fminute = fdata.getfirst("minute")
|
|
if not fminute:
|
|
minute = 0
|
|
hlist[4] = 0
|
|
else:
|
|
try:
|
|
minute = int(fminute)
|
|
except ValueError:
|
|
lerrors.append("Invalid minute.")
|
|
else:
|
|
if minute < 0 or minute > 59:
|
|
lerrors.append("Invalid minute.")
|
|
else:
|
|
hlist[4] = minute
|
|
fsecond = fdata.getfirst("second")
|
|
if not fsecond:
|
|
second = 0
|
|
hlist[5] = 0
|
|
else:
|
|
try:
|
|
second = int(fsecond)
|
|
except ValueError:
|
|
lerrors.append("Invalid second.")
|
|
else:
|
|
if second < 0 or second > 59:
|
|
lerrors.append("Invalid second.")
|
|
if second == 60 or second == 61:
|
|
lerrors.append("Leap seconds are worse than purple worms.")
|
|
else:
|
|
hlist[5] = second
|
|
if lerrors:
|
|
errlist.extend(lerrors)
|
|
return None
|
|
#return calendar.timegm([year, month, day, hour, minute, second, 0, 0, 0])
|
|
return datetime(year, month, day, hour, minute, second, 0, rlgall.utc)
|
|
|
|
# Begin processing
|
|
fdata = cgi.FieldStorage()
|
|
|
|
# If this is true, the user didn't supply any search criteria, so assume it
|
|
# doesn't want a search. Otherwise, every error or omission must be printed.
|
|
isnotsearch = checkempty(fdata)
|
|
|
|
errors = []
|
|
formname = dungeon = searchtime = None
|
|
timepieces = [None, None, None, None, None, None]
|
|
|
|
if not isnotsearch:
|
|
formname = processname(fdata, errors)
|
|
dungeon = processgame(fdata, errors)
|
|
searchtime = processtime(fdata, errors, timepieces)
|
|
|
|
dosearch = formname != None and dungeon != None and searchtime != None
|
|
|
|
# Find the actual files, and put them in a list called gamefiles.
|
|
gamefiles = []
|
|
if dosearch:
|
|
query1 = "SELECT ttyrecs FROM {0} WHERE name = %s AND startt <= %s AND endt >= %s;".format(dungeon.uname)
|
|
query2 = "SELECT ttyrecs FROM {0} WHERE name = %s AND endt >= %s ORDER BY endt LIMIT 1;".format(dungeon.uname)
|
|
query3 = "SELECT ttyrecs FROM {0} WHERE name = %s AND startt <= %s ORDER BY startt DESC LIMIT 1;".format(dungeon.uname)
|
|
conn = rlgall.getconn()
|
|
cur = conn.cursor()
|
|
cur.execute(query1, [formname, searchtime, searchtime])
|
|
result = cur.fetchone()
|
|
if result:
|
|
gamefiles = result[0]
|
|
else:
|
|
cur.execute(query2, [formname, searchtime])
|
|
result = cur.fetchone()
|
|
if result:
|
|
gamefiles = result[0]
|
|
else:
|
|
cur.execute(query3, [formname, searchtime])
|
|
result = cur.fetchone()
|
|
if result:
|
|
gamefiles = result[0]
|
|
cur.close()
|
|
conn.close()
|
|
|
|
# Now we are ready to print the page.
|
|
sys.stdout.write("Content-type: text/html\r\n\r\n")
|
|
|
|
sys.stdout.write(rlgall.phead.format("Archive"))
|
|
sys.stdout.write(rlgall.ptop)
|
|
sys.stdout.write(rlgall.navtop.format("Archive"))
|
|
sys.stdout.write('<div class="content">\n')
|
|
sys.stdout.write(rlgall.pti.format("Guild Archives"))
|
|
|
|
if dosearch:
|
|
sys.stdout.write("<p>Expedition by {0} to {1} about {2}:</p>\n".format(
|
|
rlgall.playerlink(formname), dungeon.name,
|
|
searchtime.strftime("%Y/%m/%d %H:%M:%S")))
|
|
if not gamefiles:
|
|
sys.stdout.write("<p>No record found.</p>\n")
|
|
elif len(gamefiles) == 1:
|
|
sys.stdout.write('<p><a href="/ttyrecs/{0}/{1}/{2}">1 ttyrec found.</a>\
|
|
</p>\n'.format(formname, dungeon.uname, gamefiles[0]))
|
|
else:
|
|
sys.stdout.write('<p>{0}-part ttyrec found.</p>\n'.format(len(gamefiles)))
|
|
sys.stdout.write('<ul>\n')
|
|
for i, afile in enumerate(gamefiles):
|
|
sys.stdout.write('<li><a href="/ttyrecs/{0}/{1}/{2}">Section {3}</a>\
|
|
</li>\n'.format(formname, dungeon.uname, afile, i + 1))
|
|
sys.stdout.write('</ul>\n')
|
|
if isnotsearch:
|
|
sys.stdout.write(infop)
|
|
else:
|
|
# There was information, but not good enough, i.e. errors.
|
|
sys.stdout.write('<p>')
|
|
for errmsg in errors:
|
|
sys.stdout.write(errmsg + " ")
|
|
sys.stdout.write('</p>\n')
|
|
# Print out a search form, whether a search was done or not.
|
|
sys.stdout.write('<form action="/archive.cgi" method="get">\n')
|
|
if dungeon != None:
|
|
input_game(sys.stdout, dungeon.uname)
|
|
else:
|
|
input_game(sys.stdout)
|
|
if formname != None:
|
|
input_name(sys.stdout, formname)
|
|
else:
|
|
input_name(sys.stdout)
|
|
input_datetime(sys.stdout, timepieces)
|
|
if dosearch:
|
|
sys.stdout.write('<div><input type="submit" value="Search Again"></div></form>')
|
|
else:
|
|
sys.stdout.write('<div><input type="submit" value="Search the Archive"></div></form>')
|
|
|
|
sys.stdout.write('<p>Or <a href="/ttyrecs/">browse all ttyrecs</a>.</p>')
|
|
sys.stdout.write('<p><a href="/about/ttyrec.html">What are ttyrecs?</a></p>')
|
|
sys.stdout.write("</div>\n</body>\n</html>\n")
|
|
exit()
|