Mercurial > hg > rlgallery-misc
changeset 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 | def7fecbd437 | 
| files | py/cleandb.py py/recorder.py py/rlgalldb.py py/setupdb.py py/stats.py web/archive.cgi | 
| diffstat | 6 files changed, 937 insertions(+), 0 deletions(-) [+] | 
line wrap: on
 line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/cleandb.py Wed Jul 25 21:59:42 2012 -0700 @@ -0,0 +1,20 @@ +#!/usr/bin/python +# cleandb.py: empty the database in an orderly fashion + +import rlgalldb as rlgall +import psycopg2 + +dbconn = psycopg2.connect("dbname=rlg") +dbcur = dbconn.cursor() + +dbcur.execute("UPDATE games SET offbytes = %s", [0]) + +for game in rlgall.gamelist: + dbcur.execute("DELETE FROM " + game.uname + ";") + +dbcur.execute("DELETE FROM players;") +dbconn.commit() + +dbcur.close() +dbconn.close() +exit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/recorder.py Wed Jul 25 21:59:42 2012 -0700 @@ -0,0 +1,46 @@ +#!/usr/bin/python + +import os +import time +import calendar +import psycopg2 +import rlgalldb as rlgall + +# Contains a dir for everyone who registered +everydir = "/var/dgl/dgldir/ttyrec/" +# Contains a page for everyone we know about +#knowndir = rlgall.dbdir + "players/" + +# Contact the database +conn = psycopg2.connect("dbname=rlg") +cur = conn.cursor() + +# newnames is the list of newly registered players who are not yet in the +# database. updatenames is the set of players whose pages need updating. +cur.execute("SELECT pname FROM players;") +playersInDB = [ row[0] for row in cur.fetchall() ] +playersAll = os.listdir(everydir) +newnames = [ name for name in playersAll if name not in playersInDB ] +updatenames = set(newnames) + +# Add the new names to the database +for newplayer in newnames: + cur.execute("INSERT INTO players VALUES (%s);", [newplayer]) +conn.commit() +cur.close() +conn.close() + +# Update the database for each game. +for game in rlgall.gamelist: + updatenames.update(game.loadnew()) + +# All the databases have been updated. Now make the pages. + +# Currently the high scores for all the games are on the same page. If +# they split up, this will have to change to a Game method. +rlgall.highpage() + +for name in updatenames: + rlgall.playerpage(name) + +exit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/py/rlgalldb.py Wed Jul 25 21:59:42 2012 -0700 @@ -0,0 +1,425 @@ +# rlgalldb.py +# Module for the Roguelike Gallery, using a postgres database + +import os +import sys +import time +import calendar +import re +import psycopg2 +from datetime import datetime, tzinfo, timedelta + +# Configuration +logdir = "/var/dgl/var/games/roguelike/" +webdir = "/var/www/lighttpd/scoring/" +ppagename = webdir + "players/{0}.html" +hpagename = webdir + "highscores.html" + +# HTML fragments for templating +phead = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html><head> +<title>{0}</title> +<link rel="stylesheet" href="/scoring/scores.css" type="text/css"> +</head> +""" + +ptop = """<body> +<h1>Yendor Guild</h1> +""" + +navtop = '<div class="nav"><a href="/">rlgallery.org</a> -> {0}</div>\n' +navscore = '<div class="nav"><a href="/">rlgallery.org</a> -> \ +<a href="/scoring/">Scores</a> -> {0}</div>\n' +navplayer = '<div class="nav"><a href="/">rlgallery.org</a> -> \ +<a href="/scoring/">Scores</a> -> <a href="/scoring/players/">Players</a> \ +-> {0}</div>' + +pti = '<h2>{0}</h2>\n' + +secthead = '<h3>{0}</h3>\n' +tblhead = '<div class="stable">\n' +rowstart = '<div class="sentry">\n' +rowend = '</div>\n' +cell = ' <span class="sdata">{0}</span>\n' +hcell = ' <span class="shdata">{0}</span>\n' +tblend = '</div>\n' +pend = "</body></html>\n" + +# This would be more useful if we had to do translation +headerbook = {"etime":"End time", "score":"Score", "name":"Name", "xl":"XL", + "fate":"Fate", "rank":"Rank", "game":"Game"} +# Queries for the games table +offselstr = "SELECT offbytes FROM games WHERE gname = %s;" +newoffstr = "UPDATE games SET offbytes = %s WHERE gname = %s;" + +# A representation of the UTC time zone. They say Py3k can better handle +# this madness. +class UTC(tzinfo): + def utcoffset(self, dt): + return timedelta(0) + def dst(self, dt): + return timedelta(0) + def tzname(self, dt): + return "UTC" +utc = UTC() + +def getconn(): + "Returns a database connection, or None if the connection fails." + try: + conn = psycopg2.connect("dbname=rlg") + except psycopg2.OperationalError: + return None + return conn + +def recnameToInt(filename): + recre = r"(\d{4})-(\d{2})-(\d{2})\.(\d{2}):(\d{2}):(\d{2})\.ttyrec$" + match = re.match(recre, filename) + if not match: + return None + return calendar.timegm([int(val) for val in match.groups()]) + +def recnameToTS(filename): + pattern = "%Y-%m-%d.%H:%M:%S.ttyrec" + try: + dt = datetime.strptime(filename, pattern) + dt.replace(tzinfo=utc) + return dt + except ValueError: + return None + +def ttyreclink(text, name, game, gtime): + "Returns a link to the ttyrec archivist" + lstr = '<a href="/archive.cgi?name={0};game={1};time={2}">{3}</a>' + return lstr.format(name, game, gtime, text) + +def playerlink(name): + "Returns a link to a player's page" + lstr = '<a href="/scoring/players/' + name + '.html">' + name + '</a>' + return lstr + +def linktoArchive(entry): + "Takes an entry dict and returns a link to the ttyrec archivist." + lstr = '<a href="/archive.cgi?name={0};game={1};time={2}">{3}</a>' + #linktext = time.strftime("%Y/%m/%d %H:%M:%S", time.gmtime(entry["etime"])) + linktext = entry["etstamp"].strftime("%Y/%m/%d %H:%M:%S") + return lstr.format(entry["name"], entry["game"].uname, entry["etime"], + linktext) + +def maketablerow(cells, isheader=None): + "Takes a list of strings and returns a HTML table row with each string \ + in its own cell. isheader will make them header cells, obviously." + if isheader: + celler = hcell + else: + celler = cell + rowstr = rowstart + for entry in cells: + rowstr = rowstr + celler.format(entry) + rowstr = rowstr + rowend + return rowstr + +def printTable(entries, fields, of): + "Takes a list of entry dicts and a list of field strings and writes a \ + HTML table to of." + of.write(tblhead) + clist = [] + for field in fields: + if field in headerbook: + clist.append(headerbook[field]) + else: + clist.append("{0}".format(field)) + of.write(maketablerow(clist, True)) + rnum = 0 + for i, entry in enumerate(entries): + clist = [] + for field in fields: + if field == "rank": + clist.append("{0}".format(i + 1)) + elif field in entry: + thing = entry[field] + if field == "game": + clist.append(thing.name) + elif field == "xl" or field == "score": # Numerics + clist.append(str(thing)) + elif field == "name": + clist.append(playerlink(thing)) + elif field == "fate": + clist.append(thing) + elif field == "etime": + clist.append(linktoArchive(entry)) + else: + clist.append("{0}".format(thing)) + else: + clist.append("N/A") + of.write(maketablerow(clist)) + of.write(tblend) + return + +def readentries(entfile, entlist): + "Reads a list of entries from a file object" + while True: + nextentry = entfile.readline() + if not nextentry: + break + if nextentry[-1] != '\n': # The line is incomplete + break + entlist.append(nextentry.split(None, 4)) + return + +class Game: + pass + +class RogueGame(Game): + def __init__(self, name, uname, suffix): + self.name = name + self.uname = uname + self.scores = logdir + uname + ".log" + self.logspec = ["etime", "score", "name", "xl", "fate"] + self.sqltypes = {"etime": "int", "score": "int", "name": "varchar(20)", + "xl": "int", "fate": "text", "stime": "int", + "ttyrecs": "text ARRAY", "ststamp": "timestamptz", + "etstamp": "timestamptz"} + self.logdelim = " " + # Class variables, used by some methods + fields = ["name", "score", "xl", "fate", "etime"] + rankfields = ["rank", "score", "name", "xl", "fate", "etime"]
