changeset 30:e8f3b7994d88

Port to Python 3. All scripts and modules have been ported to Python 3 and appear to work. Some changes to the lighttpd configuration were needed.
author John "Elwin" Edwards
date Tue, 31 Dec 2013 13:36:19 -0500
parents 23a769aa487e
children 7303535b5a5d
files lighttpd/conf.d/cgi.conf lighttpd/lighttpd.conf lighttpd/modules.conf py/cleandb.py py/recorder.py py/rlgalldb.py py/setupdb.py web/archive.cgi web/recent.cgi web/scoring/players/index.cgi
diffstat 10 files changed, 58 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/lighttpd/conf.d/cgi.conf	Thu Oct 31 14:33:50 2013 -0700
+++ b/lighttpd/conf.d/cgi.conf	Tue Dec 31 13:36:19 2013 -0500
@@ -13,7 +13,7 @@
 ## For PHP don't forget to set cgi.fix_pathinfo = 1 in the php.ini.
 ##
 cgi.assign                 = ( ".pl"  => "/usr/bin/perl",
-                               ".cgi" => "/usr/bin/python",
+                               ".cgi" => "",
                                ".rb"  => "/usr/bin/ruby",
                                ".erb" => "/usr/bin/eruby",
                                ".py"  => "/usr/bin/python" )
--- a/lighttpd/lighttpd.conf	Thu Oct 31 14:33:50 2013 -0700
+++ b/lighttpd/lighttpd.conf	Tue Dec 31 13:36:19 2013 -0500
@@ -451,3 +451,7 @@
 #include_shell "cat /etc/lighttpd/vhosts.d/*.conf"
 ##
 #######################################################################
+
+setenv.add-environment = (
+  "LC_CTYPE" => "en_US.utf8"
+)
--- a/lighttpd/modules.conf	Thu Oct 31 14:33:50 2013 -0700
+++ b/lighttpd/modules.conf	Tue Dec 31 13:36:19 2013 -0500
@@ -48,7 +48,7 @@
 #  "mod_evasive",
 #  "mod_redirect",
 #  "mod_rewrite",
-#  "mod_setenv",
+  "mod_setenv",
 #  "mod_usertrack",
 )
 
--- a/py/cleandb.py	Thu Oct 31 14:33:50 2013 -0700
+++ b/py/cleandb.py	Tue Dec 31 13:36:19 2013 -0500
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # cleandb.py: empty the database in an orderly fashion
 
 import rlgalldb as rlgall
--- a/py/recorder.py	Thu Oct 31 14:33:50 2013 -0700
+++ b/py/recorder.py	Tue Dec 31 13:36:19 2013 -0500
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import os
 import psycopg2
--- a/py/rlgalldb.py	Thu Oct 31 14:33:50 2013 -0700
+++ b/py/rlgalldb.py	Tue Dec 31 13:36:19 2013 -0500
@@ -1,10 +1,11 @@
 # rlgalldb.py
 # Module for the Roguelike Gallery, using a postgres database
+# Requires Python 3.3
 
 import os
-import calendar
 import psycopg2
-from datetime import datetime, tzinfo, timedelta
+from datetime import datetime
+import pytz
 
 # Configuration
 logdir = "/var/dgl/var/games/roguelike/"
@@ -49,17 +50,6 @@
 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:
@@ -71,7 +61,7 @@
 def recnameToTS(filename):
   pattern = "%Y-%m-%d.%H:%M:%S.ttyrec"
   try:
-    dt = datetime.strptime(filename, pattern).replace(tzinfo=utc)
+    dt = datetime.strptime(filename, pattern).replace(tzinfo=pytz.utc)
     return dt
   except ValueError:
     return None
@@ -90,7 +80,7 @@
   "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 = entry["endt"].strftime("%Y/%m/%d %H:%M:%S")
-  stamp = calendar.timegm(entry["endt"].utctimetuple())
+  stamp = int(entry["endt"].timestamp())
   return lstr.format(entry["name"], entry["game"].uname, stamp, linktext)
 
 def maketablerow(cells, isheader=None):
@@ -156,7 +146,7 @@
       if self.sqltypes[item] == "bool":
         ndict[item] = bool(int(value))
       elif self.sqltypes[item] == "timestamptz":
-        ndict[item] = datetime.fromtimestamp(int(value), utc)
+        ndict[item] = datetime.fromtimestamp(int(value), pytz.utc)
       else:
         ndict[item] = value
     return ndict
@@ -181,7 +171,7 @@
     offset = cur.fetchone()[0]
     newlist = []
     try:
-      scr = open(self.scores)
+      scr = open(self.scores, encoding="utf-8")
       scr.seek(offset)
       self.getEntryDicts(scr, newlist)
     except IOError:
@@ -215,7 +205,7 @@
       if result:
         prev = result[0]
       else:
-        prev = datetime.fromtimestamp(0, utc);
+        prev = datetime.fromtimestamp(0, pytz.utc);
       ttyrecdir = "/var/dgl/dgldir/ttyrec/{0}/{1}/".format(nameF, self.uname)
       allfilekeys = [ (recnameToTS(f), f) for f in os.listdir(ttyrecdir) ]
       vfilekeys = [ e for e in allfilekeys if e[0] > prev ]
@@ -242,16 +232,17 @@
     "Prints the most recent games from the logfile, NOT the database."
     newest = []
     try:
-      scr = open(self.scores)
-    except IOError:
+      scr = open(self.scores, encoding="utf-8")
+    except FileNotFoundError:
       pass
     else:
+      # Text streams don't support random seeking.
       try:
-        scr.seek(self.lookback, 2)
-      except IOError:
-        scr.seek(0) # The file wasn't that long, start at the beginning
-      if scr.tell() != 0:
-        scr.readline() # Throw away the incomplete line
+        scr.buffer.seek(self.lookback, 2)
+      except OSError:
+        scr.buffer.seek(0) # The file wasn't that long, start at the beginning
+      if scr.buffer.tell() != 0:
+        scr.buffer.readline() # Throw away the incomplete line
       self.getEntryDicts(scr, newest)
       newest.reverse()
       scr.close()
@@ -274,18 +265,11 @@
             "ttyrecs": "text ARRAY", "startt": "timestamptz"}
     self.logdelim = " "
     self.lookback = -1500
-    colspec = "("
-    valspec = "("
-    for i, col in enumerate(self.sqltypes.keys()):
-      colspec += col
-      valspec += "%({0})s".format(col)
-      if i == len(self.sqltypes) - 1:
-        colspec += ")"
-        valspec += ")"
-      else:
-        colspec += ", "
-        valspec += ", "
-    self.insertq = "INSERT INTO {0} {1} VALUES {2};".format(self.uname, 
+    # Construct the insert query
+    fields = self.sqltypes.keys()
+    colspec = ", ".join(fields)
+    valspec = ", ".join([ "%({})s".format(c) for c in fields ])
+    self.insertq = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, 
                     colspec, valspec)
   # Class variables, used by some methods
   fields = ["name", "score", "xl", "fate", "endt"]
@@ -380,11 +364,12 @@
   def putIntoDB(self, dictlist, conn):
     "Add the entries in dictlist to the database through connection conn, \
      which needs the INSERT privilege."
-    # FIXME this monster needs to be procedurally generated
-    qstr = "INSERT INTO " + self.uname + " (endt, score, name, xl, fate, ttyrecs, startt) \
-            VALUES (%(endt)s, %(score)s, %(name)s, %(xl)s, %(fate)s, %(ttyrecs)s, %(startt)s);"
+    fields = self.sqltypes.keys()
+    fstr = ", ".join(fields)
+    vstr = ", ".join([ "%({})s".format(c) for c in fields ])
+    qstr = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, fstr, vstr);
     cur = conn.cursor()
-    cur.executemany(qstr, [ d for d in dictlist if d["game"] == self ])
+    cur.executemany(self.insertq, [ d for d in dictlist if d["game"] == self ])
     conn.commit()
     cur.close()
     return
@@ -403,18 +388,11 @@
             "fate": "text", "ttyrecs": "text ARRAY", "startt": "timestamptz"}
     self.logdelim = " "
     self.lookback = -1800
-    colspec = "("
-    valspec = "("
-    for i, col in enumerate(self.sqltypes.keys()):
-      colspec += col
-      valspec += "%({0})s".format(col)
-      if i == len(self.sqltypes) - 1:
-        colspec += ")"
-        valspec += ")"
-      else:
-        colspec += ", "
-        valspec += ", "
-    self.insertq = "INSERT INTO {0} {1} VALUES {2};".format(self.uname, 
+    # Construct the insert query
+    fields = self.sqltypes.keys()
+    colspec = ", ".join(fields)
+    valspec = ", ".join([ "%({})s".format(c) for c in fields ])
+    self.insertq = "INSERT INTO {0} ({1}) VALUES ({2});".format(self.uname, 
                     colspec, valspec)
   # Class variables
   fields = ["name", "score", "class", "xl", "fate", "endt"]
@@ -492,7 +470,7 @@
 def playerpage(pname):
   "Generate a player's HTML page"
   # Write the beginning of the page
-  ppagefi = open(ppagename.format(pname), "w")
+  ppagefi = open(ppagename.format(pname), "w", encoding="utf-8")
   ppagefi.write(phead.format(pname))
   ppagefi.write(ptop)
   ppagefi.write(navplayer.format(pname))
@@ -509,7 +487,7 @@
       scoresum = 0
       for entry in entries:
         scoresum += int(entry["score"])
-      avgscr = scoresum / len(entries)
+      avgscr = scoresum // len(entries)
       ppagefi.write("<p>Average score: {0}</p>\n".format(avgscr))
   # That should settle it.  Print the end; then stop.
   ppagefi.write(pend)
@@ -518,7 +496,7 @@
 
 def highpage():
   # open the page and print the beginning
-  highpfi = open(hpagename, "w")
+  highpfi = open(hpagename, "w", encoding="utf-8")
   highpfi.write(phead.format("High Scores"))
   highpfi.write(ptop)
   highpfi.write(navscore.format("High Scores"))
--- a/py/setupdb.py	Thu Oct 31 14:33:50 2013 -0700
+++ b/py/setupdb.py	Tue Dec 31 13:36:19 2013 -0500
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # setupdb.py: initializes the database tables used by the rlg system.
 
 import rlgalldb as rlgall
--- a/web/archive.cgi	Thu Oct 31 14:33:50 2013 -0700
+++ b/web/archive.cgi	Tue Dec 31 13:36:19 2013 -0500
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import cgi
 import os
@@ -6,6 +6,7 @@
 import time
 import calendar
 from datetime import datetime
+import pytz
 import rlgalldb as rlgall
 #import cgitb
 
@@ -55,7 +56,7 @@
   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)
+  now = datetime.now(pytz.utc)
   if dvals[0] == None:
     dvals[0] = now.year
   if dvals[1] == None:
@@ -164,7 +165,7 @@
     chtime = time.gmtime(utime)
     for i in range(6):
       hlist[i] = chtime[i]
-    return datetime.fromtimestamp(utime, rlgall.utc)
+    return datetime.fromtimestamp(utime, pytz.utc)
 
   # Now try to get a human-readable specification.
   lerrors = []
@@ -261,7 +262,7 @@
     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)
+  return datetime(year, month, day, hour, minute, second, 0, pytz.utc)
 
 # Begin processing
 fdata = cgi.FieldStorage()
@@ -307,7 +308,7 @@
   conn.close()
 
 # Now we are ready to print the page.
-sys.stdout.write("Content-type: text/html\r\n\r\n")
+sys.stdout.write("Content-Type: text/html; charset=utf-8\r\n\r\n")
 
 sys.stdout.write(rlgall.phead.format("Archive"))
 sys.stdout.write(rlgall.ptop)
--- a/web/recent.cgi	Thu Oct 31 14:33:50 2013 -0700
+++ b/web/recent.cgi	Tue Dec 31 13:36:19 2013 -0500
@@ -1,12 +1,16 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # CGI script creating page of recent games
 
 import sys
 import time
 import rlgalldb as rlgall
 
+# It is assumed that sys.stdout uses UTF-8 encoding.  If this is not the case,
+# configure the Web server to set the LC_CTYPE environment variable to a UTF-8
+# locale.
+
 # The required header
-sys.stdout.write("Content-type: text/html\r\n\r\n")
+sys.stdout.write("Content-Type: text/html; charset=utf-8\r\n\r\n")
 # The top of the page
 sys.stdout.write(rlgall.phead.format("Recent Games"))
 sys.stdout.write(rlgall.ptop);
--- a/web/scoring/players/index.cgi	Thu Oct 31 14:33:50 2013 -0700
+++ b/web/scoring/players/index.cgi	Tue Dec 31 13:36:19 2013 -0500
@@ -1,8 +1,8 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import os
 import sys
-import rlgall
+import rlgalldb as rlgall
 
 playerdir = "/var/www/lighttpd/scoring/players/"
 linkstr = '<li><a href="./{0}.html">{0}</a></li>\n'
@@ -15,7 +15,7 @@
     namelist.append(filename.rsplit(".html", 1)[0])
 namelist.sort()
 
-sys.stdout.write("Content-type: text/html\r\n\r\n")
+sys.stdout.write("Content-Type: text/html; charset=utf-8\r\n\r\n")
 sys.stdout.write(rlgall.phead.format("Players"))
 sys.stdout.write(rlgall.ptop)
 sys.stdout.write(rlgall.navscore.format("Players"))