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: