view 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 source

#!/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()