Mercurial > hg > rlgallery-misc
diff web/archive.cgi @ 0:5ba2123d2c20
Put this project under version control, finally.
Scripts for rlgallery.org, using a PostgreSQL backend. The recorder
system is in py/, CGI scripts are in web/.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Wed, 25 Jul 2012 21:59:42 -0700 |
parents | |
children | 8f49df4074d7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/archive.cgi Wed Jul 25 21:59:42 2012 -0700 @@ -0,0 +1,374 @@ +#!/usr/bin/python + +import cgi +import os +import sys +import re +import time +import calendar +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/" +recre = r"(\d{4,4})-(\d{2,2})-(\d{2,2})\.(\d{2,2}):(\d{2,2}):(\d{2,2})\.ttyrec" +recrec = re.compile(recre) + +def name_in_time(filename, timerange): + "Checks whether filename is a ttyrec with time between Unix times \ + timerange[0] and timerange[1]." + nmatch = recrec.match(filename) + if not nmatch: + return False + ntime = calendar.timegm([int(val) for val in nmatch.groups()]) + if ntime > timerange[0] and ntime <= timerange[1]: + return True + else: + return False + +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 = time.gmtime() + for dindex in range(3): + if not dvals[dindex]: + dvals[dindex] = now[dindex] + for year in range(2010, now[0] + 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 Unix timestamp 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 utime + + # 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]) + +# 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 = utime = None +timepieces = [None, None, None, None, None, None] + +if not isnotsearch: + formname = processname(fdata, errors) + dungeon = processgame(fdata, errors) + utime = processtime(fdata, errors, timepieces) + +dosearch = formname != None and dungeon != None and utime != None + +# Find the actual files, and put them in a list called gamefiles. +gtimes = [0, int(time.time())] +relgame = None +gamefiles = [] +if dosearch: + ttyrecdir = "{0}/{1}/{2}/".format(ttyrecbase, formname, dungeon.uname) + query1 = "SELECT ttyrecs FROM {0} WHERE name = %s AND stime <= %s AND etime >= %s;".format(dungeon.uname) + query2 = "SELECT ttyrecs FROM {0} WHERE name = %s AND etime >= %s ORDER BY etime LIMIT 1;".format(dungeon.uname) + query3 = "SELECT ttyrecs FROM {0} WHERE name = %s AND stime <= %s ORDER BY stime DESC LIMIT 1;".format(dungeon.uname) + conn = rlgall.getconn() + cur = conn.cursor() + cur.execute(query1, [formname, utime, utime]) + result = cur.fetchone() + if result: + gamefiles = result[0] + else: + cur.execute(query2, [formname, utime]) + result = cur.fetchone() + if result: + gamefiles = result[0] + else: + cur.execute(query3, [formname, utime]) + 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, + time.strftime("%Y/%m/%d %H:%M:%S", time.gmtime(utime)))) + 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="/archivedb.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()