Mercurial > hg > rlgallery-misc
comparison 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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:5ba2123d2c20 |
|---|---|
| 1 #!/usr/bin/python | |
| 2 | |
| 3 import cgi | |
| 4 import os | |
| 5 import sys | |
| 6 import re | |
| 7 import time | |
| 8 import calendar | |
| 9 import rlgalldb as rlgall | |
| 10 import cgitb | |
| 11 | |
| 12 cgitb.enable() | |
| 13 | |
| 14 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 | |
| 15 the game was spread across? The Archivist can find them all for you.</p> | |
| 16 """ | |
| 17 | |
| 18 ttyrecbase = "/var/dgl/dgldir/ttyrec/" | |
| 19 recre = r"(\d{4,4})-(\d{2,2})-(\d{2,2})\.(\d{2,2}):(\d{2,2}):(\d{2,2})\.ttyrec" | |
| 20 recrec = re.compile(recre) | |
| 21 | |
| 22 def name_in_time(filename, timerange): | |
| 23 "Checks whether filename is a ttyrec with time between Unix times \ | |
| 24 timerange[0] and timerange[1]." | |
| 25 nmatch = recrec.match(filename) | |
| 26 if not nmatch: | |
| 27 return False | |
| 28 ntime = calendar.timegm([int(val) for val in nmatch.groups()]) | |
| 29 if ntime > timerange[0] and ntime <= timerange[1]: | |
| 30 return True | |
| 31 else: | |
| 32 return False | |
| 33 | |
| 34 def input_game(outf, selected=None): | |
| 35 "Prints the form components for selecting a game." | |
| 36 selstr = '<option label="{0}" value="{1}" selected="selected">{0}</option>\n' | |
| 37 unselstr = '<option label="{0}" value="{1}">{0}</option>\n' | |
| 38 outf.write('<div>Dungeon name: <select name="game">\n') | |
| 39 for game in rlgall.gamelist: | |
| 40 # This will cause trouble if someone puts games with identical names | |
| 41 # into rlgall. | |
| 42 if selected and selected == game.uname: | |
| 43 outf.write(selstr.format(game.name, game.uname)) | |
| 44 else: | |
| 45 outf.write(unselstr.format(game.name, game.uname)) | |
| 46 outf.write('</select></div>\n') | |
| 47 return | |
| 48 | |
| 49 def input_name(outf, defaultval=None): | |
| 50 defstr = '<div>Adventurer\'s name: <input type="text" name="name" value="{0}"></div>\n' | |
| 51 if defaultval: | |
| 52 outf.write(defstr.format(defaultval)) | |
| 53 else: | |
| 54 outf.write('<div>Adventurer\'s Name: <input type="text" name="name"></div>\n') | |
| 55 return | |
| 56 | |
| 57 def input_time(outf, defaultval=None): | |
| 58 defstr = '<div>Time: <input type="text" name="time" value="{0}"></div>\n' | |
| 59 if defaultval: | |
| 60 outf.write(defstr.format(defaultval)) | |
| 61 else: | |
| 62 outf.write('<div>Time: <input type="text" name="time"></div>\n') | |
| 63 return | |
| 64 | |
| 65 def input_datetime(outf, dvals=[None, None, None, None, None, None]): | |
| 66 optstr = '<option value="{0}" label="{1}">{1}</option>\n' | |
| 67 soptstr = '<option value="{0}" label="{1}" selected="selected">{1}</option>\n' | |
| 68 tf = '<input type="text" size="2" maxlength="2" name="{0}" value="{1:02}">' | |
| 69 emptf = '<input type="text" size="2" maxlength="2" name="{0}">' | |
| 70 sstr = '<div>Date: <select name="year">\n' | |
| 71 # Default to today | |
| 72 now = time.gmtime() | |
| 73 for dindex in range(3): | |
| 74 if not dvals[dindex]: | |
| 75 dvals[dindex] = now[dindex] | |
| 76 for year in range(2010, now[0] + 1): | |
| 77 if year == dvals[0]: | |
| 78 sstr += soptstr.format(year, year) | |
| 79 else: | |
| 80 sstr += optstr.format(year, year) | |
| 81 sstr += '</select>\n' | |
| 82 outf.write(sstr) | |
| 83 sstr = '<select name="month">\n' | |
| 84 for month in range(1, 13): | |
| 85 if month == dvals[1]: | |
| 86 sstr += soptstr.format(month, calendar.month_name[month]) | |
| 87 else: | |
| 88 sstr += optstr.format(month, calendar.month_name[month]) | |
| 89 sstr += '</select>\n' | |
| 90 outf.write(sstr) | |
| 91 sstr = '<select name="day">\n' | |
| 92 for day in range(1, 32): | |
| 93 if day == dvals[2]: | |
| 94 sstr += soptstr.format(day, day) | |
| 95 else: | |
| 96 sstr += optstr.format(day, day) | |
| 97 sstr += '</select></div>\n' | |
| 98 outf.write(sstr) | |
| 99 sstr = '<div>Approximate time: ' | |
| 100 if dvals[3] != None: | |
| 101 sstr += tf.format("hour", dvals[3]) | |
| 102 else: | |
| 103 sstr += emptf.format("hour") | |
| 104 sstr += " : " | |
| 105 if dvals[4] != None: | |
| 106 sstr += tf.format("minute", dvals[4]) | |
| 107 else: | |
| 108 sstr += emptf.format("minute") | |
| 109 sstr += " : " | |
| 110 if dvals[5] != None: | |
| 111 sstr += tf.format("second", dvals[5]) | |
| 112 else: | |
| 113 sstr += emptf.format("second") | |
| 114 sstr += ' (optional)</div>\n' | |
| 115 outf.write(sstr) | |
| 116 return | |
| 117 | |
| 118 def checkempty(cgidata): | |
| 119 exkeys = set(cgidata.keys()) | |
| 120 wanted = set(["name", "game", "time", "year", "month", "day", "hour", | |
| 121 "minute", "second"]) | |
| 122 return wanted.isdisjoint(exkeys) | |
| 123 | |
| 124 def processname(fdata, errlist): | |
| 125 "Takes a CGI data object, extracts the player name, checks its validity, \ | |
| 126 and returns it. If errors are encountered, they are appended to errlist." | |
| 127 cantfind = "I have no record of an adventurer called {0}." | |
| 128 if "name" not in fdata: | |
| 129 errlist.append("You didn\'t tell me the adventurer\'s name.") | |
| 130 return None | |
| 131 # Just in case someone supplies something nasty, make sure they can't go | |
| 132 # digging all over the filesystem. | |
| 133 formname = fdata.getfirst("name").rpartition("/")[2] | |
| 134 try: | |
| 135 os.stat(ttyrecbase + formname) | |
| 136 except OSError: | |
| 137 errlist.append(cantfind.format(cgi.escape(formname))) | |
| 138 return None | |
| 139 return formname | |
| 140 | |
| 141 def processgame(fdata, errlist): | |
| 142 "Takes a CGI data object and returns the game from rlgall.gamelist that \ | |
| 143 it asks for. Errors appended to errlist." | |
| 144 cantfind = "I've never heard of a dungeon called {0}." | |
| 145 if "game" not in fdata: | |
| 146 errlist.append('You didn\'t choose a dungeon.') | |
| 147 return None | |
| 148 formgame = fdata.getfirst("game") | |
| 149 for agame in rlgall.gamelist: | |
| 150 if agame.uname == formgame: | |
| 151 return agame | |
| 152 errlist.append(cantfind.format(cgi.escape(formgame))) | |
| 153 return None | |
| 154 | |
| 155 def processtime(fdata, errlist, hlist): | |
| 156 "Takes a CGI data object and converts to a Unix timestamp by finding fields \ | |
| 157 called year, month, etc. Any errors get appended to errlist. hlist \ | |
| 158 should contain 6 components, for ymd-hms fields." | |
| 159 | |
| 160 # Timestamp overrides human-readable, even if it's invalid. | |
| 161 badtime = 'The time field is for Unix clocks, which never say anything \ | |
| 162 like {0}. Try the form instead.' | |
| 163 utime = None | |
| 164 formtime = fdata.getfirst("time") | |
| 165 if formtime: | |
| 166 try: | |
| 167 utime = int(formtime) | |
| 168 except ValueError: | |
| 169 errlist.append(badtime.format(cgi.escape(formtime))) | |
| 170 return None | |
| 171 else: | |
| 172 if utime < 0: | |
| 173 utime = 0 | |
| 174 if utime != None: | |
| 175 chtime = time.gmtime(utime) | |
| 176 for i in range(6): | |
| 177 hlist[i] = chtime[i] | |
| 178 return utime | |
| 179 | |
| 180 # Now try to get a human-readable specification. | |
| 181 lerrors = [] | |
| 182 year = month = day = hour = minute = second = None | |
| 183 fyear = fdata.getfirst("year") | |
| 184 if not fyear: | |
| 185 lerrors.append("No year was provided.") | |
| 186 else: | |
| 187 try: | |
| 188 year = int(fyear) | |
| 189 except ValueError: | |
| 190 lerrors.append("Invalid year.") | |
| 191 else: | |
| 192 if year < 2010: | |
| 193 lerrors.append("The Gallery has only existed since 2010.") | |
| 194 elif year > time.gmtime()[0]: | |
| 195 lerrors.append("I can't see into the future.") | |
| 196 else: | |
| 197 hlist[0] = year # It is valid | |
| 198 fmonth = fdata.getfirst("month") | |
| 199 if not fmonth: | |
| 200 lerrors.append("No month was provided.") | |
| 201 else: | |
| 202 try: | |
| 203 month = int(fmonth) | |
| 204 except ValueError: | |
| 205 lerrors.append("Invalid month.") | |
| 206 else: | |
| 207 if month < 1 or month > 12: | |
| 208 lerrors.append("Invalid month.") | |
| 209 else: | |
| 210 hlist[1] = month | |
| 211 fday = fdata.getfirst("day") | |
| 212 if not fday: | |
| 213 lerrors.append("No day was provided.") | |
| 214 else: | |
| 215 try: | |
| 216 day = int(fday) | |
| 217 except ValueError: | |
| 218 lerrors.append("Invalid day.") | |
| 219 else: | |
| 220 if day < 1 or day > 31: | |
| 221 lerrors.append("Invalid day.") | |
| 222 elif not lerrors: | |
| 223 if day > calendar.monthrange(year, month)[1]: | |
| 224 lerrors.append("Invalid day.") | |
| 225 else: | |
| 226 hlist[2] = day | |
| 227 fhour = fdata.getfirst("hour") | |
| 228 if not fhour: | |
| 229 hour = 0 # Assume things. | |
| 230 hlist[3] = 0 | |
| 231 else: | |
| 232 try: | |
| 233 hour = int(fhour) | |
| 234 except ValueError: | |
| 235 lerrors.append("Invalid hour.") | |
| 236 else: | |
| 237 if hour < 0 or hour > 23: | |
| 238 lerrors.append("Invalid hour.") | |
| 239 else: | |
| 240 hlist[3] = hour | |
| 241 fminute = fdata.getfirst("minute") | |
| 242 if not fminute: | |
| 243 minute = 0 | |
| 244 hlist[4] = 0 | |
| 245 else: | |
| 246 try: | |
| 247 minute = int(fminute) | |
| 248 except ValueError: | |
| 249 lerrors.append("Invalid minute.") | |
| 250 else: | |
| 251 if minute < 0 or minute > 59: | |
| 252 lerrors.append("Invalid minute.") | |
| 253 else: | |
| 254 hlist[4] = minute | |
| 255 fsecond = fdata.getfirst("second") | |
| 256 if not fsecond: | |
| 257 second = 0 | |
| 258 hlist[5] = 0 | |
| 259 else: | |
| 260 try: | |
| 261 second = int(fsecond) | |
| 262 except ValueError: | |
| 263 lerrors.append("Invalid second.") | |
| 264 else: | |
| 265 if second < 0 or second > 59: | |
| 266 lerrors.append("Invalid second.") | |
| 267 if second == 60 or second == 61: | |
| 268 lerrors.append("Leap seconds are worse than purple worms.") | |
| 269 else: | |
| 270 hlist[5] = second | |
| 271 if lerrors: | |
| 272 errlist.extend(lerrors) | |
| 273 return None | |
| 274 return calendar.timegm([year, month, day, hour, minute, second, 0, 0, 0]) | |
| 275 | |
| 276 # Begin processing | |
| 277 fdata = cgi.FieldStorage() | |
| 278 | |
| 279 # If this is true, the user didn't supply any search criteria, so assume it | |
| 280 # doesn't want a search. Otherwise, every error or omission must be printed. | |
| 281 isnotsearch = checkempty(fdata) | |
| 282 | |
| 283 errors = [] | |
| 284 formname = dungeon = utime = None | |
| 285 timepieces = [None, None, None, None, None, None] | |
| 286 | |
| 287 if not isnotsearch: | |
| 288 formname = processname(fdata, errors) | |
| 289 dungeon = processgame(fdata, errors) | |
| 290 utime = processtime(fdata, errors, timepieces) | |
| 291 | |
| 292 dosearch = formname != None and dungeon != None and utime != None | |
| 293 | |
| 294 # Find the actual files, and put them in a list called gamefiles. | |
| 295 gtimes = [0, int(time.time())] | |
| 296 relgame = None | |
| 297 gamefiles = [] | |
| 298 if dosearch: | |
| 299 ttyrecdir = "{0}/{1}/{2}/".format(ttyrecbase, formname, dungeon.uname) | |
| 300 query1 = "SELECT ttyrecs FROM {0} WHERE name = %s AND stime <= %s AND etime >= %s;".format(dungeon.uname) | |
| 301 query2 = "SELECT ttyrecs FROM {0} WHERE name = %s AND etime >= %s ORDER BY etime LIMIT 1;".format(dungeon.uname) | |
| 302 query3 = "SELECT ttyrecs FROM {0} WHERE name = %s AND stime <= %s ORDER BY stime DESC LIMIT 1;".format(dungeon.uname) | |
| 303 conn = rlgall.getconn() | |
| 304 cur = conn.cursor() | |
| 305 cur.execute(query1, [formname, utime, utime]) | |
| 306 result = cur.fetchone() | |
| 307 if result: | |
| 308 gamefiles = result[0] | |
| 309 else: | |
| 310 cur.execute(query2, [formname, utime]) | |
| 311 result = cur.fetchone() | |
| 312 if result: | |
| 313 gamefiles = result[0] | |
| 314 else: | |
| 315 cur.execute(query3, [formname, utime]) | |
| 316 result = cur.fetchone() | |
| 317 if result: | |
| 318 gamefiles = result[0] | |
| 319 cur.close() | |
| 320 conn.close() | |
| 321 | |
| 322 # Now we are ready to print the page. | |
| 323 sys.stdout.write("Content-type: text/html\r\n\r\n") | |
| 324 | |
| 325 sys.stdout.write(rlgall.phead.format("Archive")) | |
| 326 sys.stdout.write(rlgall.ptop) | |
| 327 sys.stdout.write(rlgall.navtop.format("Archive")) | |
| 328 sys.stdout.write('<div class="content">\n') | |
| 329 sys.stdout.write(rlgall.pti.format("Guild Archives")) | |
| 330 | |
| 331 if dosearch: | |
| 332 sys.stdout.write("<p>Expedition by {0} to {1} about {2}:</p>\n".format( | |
| 333 rlgall.playerlink(formname), dungeon.name, | |
| 334 time.strftime("%Y/%m/%d %H:%M:%S", time.gmtime(utime)))) | |
| 335 if not gamefiles: | |
| 336 sys.stdout.write("<p>No record found.</p>\n") | |
| 337 elif len(gamefiles) == 1: | |
| 338 sys.stdout.write('<p><a href="/ttyrecs/{0}/{1}/{2}">1 ttyrec found.</a>\ | |
| 339 </p>\n'.format(formname, dungeon.uname, gamefiles[0])) | |
| 340 else: | |
| 341 sys.stdout.write('<p>{0}-part ttyrec found.</p>\n'.format(len(gamefiles))) | |
| 342 sys.stdout.write('<ul>\n') | |
| 343 for i, afile in enumerate(gamefiles): | |
| 344 sys.stdout.write('<li><a href="/ttyrecs/{0}/{1}/{2}">Section {3}</a>\ | |
| 345 </li>\n'.format(formname, dungeon.uname, afile, i + 1)) | |
| 346 sys.stdout.write('</ul>\n') | |
| 347 if isnotsearch: | |
| 348 sys.stdout.write(infop) | |
| 349 else: | |
| 350 # There was information, but not good enough, i.e. errors. | |
| 351 sys.stdout.write('<p>') | |
| 352 for errmsg in errors: | |
