Mercurial > hg > rlgallery-misc
comparison py/rlgalldb.py @ 1:def7fecbd437
Switch to SQL timestamps.
Use SQL timestamps whenever possible, and don't store UNIX timestamps
in the database at all.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Wed, 25 Jul 2012 23:05:12 -0700 |
parents | 5ba2123d2c20 |
children | a943cfdfbad9 |
comparison
equal
deleted
inserted
replaced
0:5ba2123d2c20 | 1:def7fecbd437 |
---|---|
44 hcell = ' <span class="shdata">{0}</span>\n' | 44 hcell = ' <span class="shdata">{0}</span>\n' |
45 tblend = '</div>\n' | 45 tblend = '</div>\n' |
46 pend = "</body></html>\n" | 46 pend = "</body></html>\n" |
47 | 47 |
48 # This would be more useful if we had to do translation | 48 # This would be more useful if we had to do translation |
49 headerbook = {"etime":"End time", "score":"Score", "name":"Name", "xl":"XL", | 49 headerbook = {"endt":"End time", "score":"Score", "name":"Name", "xl":"XL", |
50 "fate":"Fate", "rank":"Rank", "game":"Game"} | 50 "fate":"Fate", "rank":"Rank", "game":"Game"} |
51 # Queries for the games table | 51 # Queries for the games table |
52 offselstr = "SELECT offbytes FROM games WHERE gname = %s;" | 52 offselstr = "SELECT offbytes FROM games WHERE gname = %s;" |
53 newoffstr = "UPDATE games SET offbytes = %s WHERE gname = %s;" | 53 newoffstr = "UPDATE games SET offbytes = %s WHERE gname = %s;" |
54 | 54 |
79 return calendar.timegm([int(val) for val in match.groups()]) | 79 return calendar.timegm([int(val) for val in match.groups()]) |
80 | 80 |
81 def recnameToTS(filename): | 81 def recnameToTS(filename): |
82 pattern = "%Y-%m-%d.%H:%M:%S.ttyrec" | 82 pattern = "%Y-%m-%d.%H:%M:%S.ttyrec" |
83 try: | 83 try: |
84 dt = datetime.strptime(filename, pattern) | 84 dt = datetime.strptime(filename, pattern).replace(tzinfo=utc) |
85 dt.replace(tzinfo=utc) | |
86 return dt | 85 return dt |
87 except ValueError: | 86 except ValueError: |
88 return None | 87 return None |
89 | 88 |
90 def ttyreclink(text, name, game, gtime): | 89 def ttyreclink(text, name, game, gtime): |
98 return lstr | 97 return lstr |
99 | 98 |
100 def linktoArchive(entry): | 99 def linktoArchive(entry): |
101 "Takes an entry dict and returns a link to the ttyrec archivist." | 100 "Takes an entry dict and returns a link to the ttyrec archivist." |
102 lstr = '<a href="/archive.cgi?name={0};game={1};time={2}">{3}</a>' | 101 lstr = '<a href="/archive.cgi?name={0};game={1};time={2}">{3}</a>' |
103 #linktext = time.strftime("%Y/%m/%d %H:%M:%S", time.gmtime(entry["etime"])) | 102 linktext = entry["endt"].strftime("%Y/%m/%d %H:%M:%S") |
104 linktext = entry["etstamp"].strftime("%Y/%m/%d %H:%M:%S") | 103 stamp = calendar.timegm(entry["endt"].utctimetuple()) |
105 return lstr.format(entry["name"], entry["game"].uname, entry["etime"], | 104 return lstr.format(entry["name"], entry["game"].uname, stamp, linktext) |
106 linktext) | |
107 | 105 |
108 def maketablerow(cells, isheader=None): | 106 def maketablerow(cells, isheader=None): |
109 "Takes a list of strings and returns a HTML table row with each string \ | 107 "Takes a list of strings and returns a HTML table row with each string \ |
110 in its own cell. isheader will make them header cells, obviously." | 108 in its own cell. isheader will make them header cells, obviously." |
111 if isheader: | 109 if isheader: |
143 clist.append(str(thing)) | 141 clist.append(str(thing)) |
144 elif field == "name": | 142 elif field == "name": |
145 clist.append(playerlink(thing)) | 143 clist.append(playerlink(thing)) |
146 elif field == "fate": | 144 elif field == "fate": |
147 clist.append(thing) | 145 clist.append(thing) |
148 elif field == "etime": | 146 elif field == "endt": |
149 clist.append(linktoArchive(entry)) | 147 clist.append(linktoArchive(entry)) |
150 else: | 148 else: |
151 clist.append("{0}".format(thing)) | 149 clist.append("{0}".format(thing)) |
152 else: | 150 else: |
153 clist.append("N/A") | 151 clist.append("N/A") |
172 class RogueGame(Game): | 170 class RogueGame(Game): |
173 def __init__(self, name, uname, suffix): | 171 def __init__(self, name, uname, suffix): |
174 self.name = name | 172 self.name = name |
175 self.uname = uname | 173 self.uname = uname |
176 self.scores = logdir + uname + ".log" | 174 self.scores = logdir + uname + ".log" |
177 self.logspec = ["etime", "score", "name", "xl", "fate"] | 175 self.logspec = ["endt", "score", "name", "xl", "fate"] |
178 self.sqltypes = {"etime": "int", "score": "int", "name": "varchar(20)", | 176 self.sqltypes = {"endt": "timestamptz", "score": "int", |
179 "xl": "int", "fate": "text", "stime": "int", | 177 "name": "varchar(20)", "xl": "int", "fate": "text", |
180 "ttyrecs": "text ARRAY", "ststamp": "timestamptz", | 178 "ttyrecs": "text ARRAY", "startt": "timestamptz"} |
181 "etstamp": "timestamptz"} | |
182 self.logdelim = " " | 179 self.logdelim = " " |
183 # Class variables, used by some methods | 180 # Class variables, used by some methods |
184 fields = ["name", "score", "xl", "fate", "etime"] | 181 fields = ["name", "score", "xl", "fate", "endt"] |
185 rankfields = ["rank", "score", "name", "xl", "fate", "etime"] | 182 rankfields = ["rank", "score", "name", "xl", "fate", "endt"] |
186 pfields = ["score", "xl", "fate", "etime"] | 183 pfields = ["score", "xl", "fate", "endt"] |
187 def logtoDict(self, entry): | 184 def logtoDict(self, entry): |
188 "Processes a log entry string, returning a dict." | 185 "Processes a log entry string, returning a dict." |
189 ndict = {"game": self} | 186 ndict = {"game": self} |
190 entrylist = entry.strip().split(self.logdelim, len(self.logspec) - 1) | 187 entrylist = entry.strip().split(self.logdelim, len(self.logspec) - 1) |
191 for item, value in zip(self.logspec, entrylist): | 188 for item, value in zip(self.logspec, entrylist): |
192 if self.sqltypes[item] == "int": | 189 if self.sqltypes[item] == "int": |
193 ndict[item] = int(value) | 190 ndict[item] = int(value) |
191 elif self.sqltypes[item] == "timestamptz": | |
192 ndict[item] = datetime.fromtimestamp(int(value), utc) | |
194 else: | 193 else: |
195 ndict[item] = value | 194 ndict[item] = value |
196 return ndict | 195 return ndict |
197 def getEntryDicts(self, entfile, entlist): | 196 def getEntryDicts(self, entfile, entlist): |
198 "Reads logfile entries from entfile, interprets them according to the \ | 197 "Reads logfile entries from entfile, interprets them according to the \ |
215 if n <= 0: | 214 if n <= 0: |
216 return [] | 215 return [] |
217 dictlist = [] | 216 dictlist = [] |
218 conn = psycopg2.connect("dbname=rlg") | 217 conn = psycopg2.connect("dbname=rlg") |
219 cur = conn.cursor() | 218 cur = conn.cursor() |
220 qstr = "SELECT etime, score, name, xl, fate, ststamp, etstamp FROM {0} ORDER BY etstamp DESC \ | 219 qstr = "SELECT endt, score, name, xl, fate, startt FROM {0} ORDER BY endt DESC \ |
221 LIMIT %s;".format(self.uname) | 220 LIMIT %s;".format(self.uname) |
222 cur.execute(qstr, [n]) | 221 cur.execute(qstr, [n]) |
223 for record in cur: | 222 for record in cur: |
224 # This should be less hardcodish | 223 # This should be less hardcodish |
225 ndict = {"game": self} | 224 ndict = {"game": self} |
226 ndict["etime"] = record[0] | 225 ndict["endt"] = record[0] |
227 ndict["score"] = record[1] | 226 ndict["score"] = record[1] |
228 ndict["name"] = record[2] | 227 ndict["name"] = record[2] |
229 ndict["xl"] = record[3] | 228 ndict["xl"] = record[3] |
230 ndict["fate"] = record[4] | 229 ndict["fate"] = record[4] |
231 ndict["ststamp"] = record[5] | 230 ndict["startt"] = record[5] |
232 ndict["etstamp"] = record[6] | |
233 dictlist.append(ndict) | 231 dictlist.append(ndict) |
234 cur.close() | 232 cur.close() |
235 conn.close() | 233 conn.close() |
236 return dictlist | 234 return dictlist |
237 def getHigh(self, n=10, offset=0): | 235 def getHigh(self, n=10, offset=0): |
238 "Gets the n highest scores (starting at offset) from the database, \ | 236 "Gets the n highest scores (starting at offset) from the database, \ |
239 returning a list of dicts." | 237 returning a list of dicts." |
240 qstr = "SELECT etime, score, name, xl, fate, ststamp, etstamp FROM {0} ORDER BY score DESC\ | 238 qstr = "SELECT endt, score, name, xl, fate, startt FROM {0} ORDER BY score DESC ".format(self.uname) |
241 ".format(self.uname) | |
242 qvals = [] | 239 qvals = [] |
243 try: | 240 try: |
244 n = int(n) | 241 n = int(n) |
245 except (ValueError, TypeError): | 242 except (ValueError, TypeError): |
246 return [] | 243 return [] |
260 cur = conn.cursor() | 257 cur = conn.cursor() |
261 cur.execute(qstr, qvals) | 258 cur.execute(qstr, qvals) |
262 dictlist = [] | 259 dictlist = [] |
263 for record in cur: | 260 for record in cur: |
264 ndict = {"game": self} | 261 ndict = {"game": self} |
265 ndict["etime"] = record[0] | 262 ndict["endt"] = record[0] |
266 ndict["score"] = record[1] | 263 ndict["score"] = record[1] |
267 ndict["name"] = record[2] | 264 ndict["name"] = record[2] |
268 ndict["xl"] = record[3] | 265 ndict["xl"] = record[3] |
269 ndict["fate"] = record[4] | 266 ndict["fate"] = record[4] |
270 ndict["ststamp"] = record[5] | 267 ndict["startt"] = record[5] |
271 ndict["etstamp"] = record[6] | |
272 dictlist.append(ndict) | 268 dictlist.append(ndict) |
273 cur.close() | 269 cur.close() |
274 conn.close() | 270 conn.close() |
275 return dictlist | 271 return dictlist |
276 def getPlayer(self, player): | 272 def getPlayer(self, player): |
277 "Gets all player's games from the database." | 273 "Gets all player's games from the database." |
278 qstr = "SELECT etime, score, name, xl, fate, ststamp, etstamp FROM " + self.uname + " WHERE \ | 274 qstr = "SELECT endt, score, name, xl, fate, startt FROM " + self.uname + " WHERE name = %s;" |
279 name = %s;" | |
280 conn = getconn() | 275 conn = getconn() |
281 if conn == None: | 276 if conn == None: |
282 return [] | 277 return [] |
283 cur = conn.cursor() | 278 cur = conn.cursor() |
284 entries = [] | 279 entries = [] |
285 cur.execute(qstr, [player]) | 280 cur.execute(qstr, [player]) |
286 for record in cur: | 281 for record in cur: |
287 ndict = {"game": self} | 282 ndict = {"game": self} |
288 ndict["etime"] = record[0] | 283 ndict["endt"] = record[0] |
289 ndict["score"] = record[1] | 284 ndict["score"] = record[1] |
290 ndict["name"] = record[2] | 285 ndict["name"] = record[2] |
291 ndict["xl"] = record[3] | 286 ndict["xl"] = record[3] |
292 ndict["fate"] = record[4] | 287 ndict["fate"] = record[4] |
293 ndict["ststamp"] = record[5] | 288 ndict["startt"] = record[5] |
294 ndict["etstamp"] = record[6] | |
295 entries.append(ndict) | 289 entries.append(ndict) |
296 cur.close() | 290 cur.close() |
297 conn.close() | 291 conn.close() |
298 return entries | 292 return entries |
299 def putIntoDB(self, dictlist, conn): | 293 def putIntoDB(self, dictlist, conn): |
300 "Add the entries in dictlist to the database through connection conn, \ | 294 "Add the entries in dictlist to the database through connection conn, \ |
301 which needs the INSERT privilege." | 295 which needs the INSERT privilege." |
302 # FIXME this monster needs to be procedurally generated | 296 # FIXME this monster needs to be procedurally generated |
303 qstr = "INSERT INTO " + self.uname + " (etime, score, name, xl, fate, stime, ttyrecs, ststamp, etstamp) \ | 297 qstr = "INSERT INTO " + self.uname + " (endt, score, name, xl, fate, ttyrecs, startt) \ |
304 VALUES (%(etime)s, %(score)s, %(name)s, %(xl)s, %(fate)s, %(stime)s, %(ttyrecs)s, %(ststamp)s, %(etstamp)s);" | 298 VALUES (%(endt)s, %(score)s, %(name)s, %(xl)s, %(fate)s, %(ttyrecs)s, %(startt)s);" |
305 cur = conn.cursor() | 299 cur = conn.cursor() |
306 cur.executemany(qstr, [ d for d in dictlist if d["game"] == self ]) | 300 cur.executemany(qstr, [ d for d in dictlist if d["game"] == self ]) |
307 conn.commit() | 301 conn.commit() |
308 cur.close() | 302 cur.close() |
309 return | 303 return |
314 return [] | 308 return [] |
315 cur = conn.cursor() | 309 cur = conn.cursor() |
316 for nameF in names: | 310 for nameF in names: |
317 # Get all this player's games ordered by time | 311 # Get all this player's games ordered by time |
318 itsEntries = [ entry for entry in gamelist if entry["name"] == nameF ] | 312 itsEntries = [ entry for entry in gamelist if entry["name"] == nameF ] |
319 itsEntries.sort(key=lambda e: e["etime"]) | 313 itsEntries.sort(key=lambda e: e["endt"]) |
320 # Find the end time of the latest game already in the db | 314 # Find the end time of the latest game already in the db |
321 tquery = "SELECT etstamp FROM {0} WHERE name = %s ORDER BY etstamp DESC LIMIT 1;".format(self.uname) | 315 tquery = "SELECT endt FROM {0} WHERE name = %s ORDER BY endt DESC LIMIT 1;".format(self.uname) |
322 cur.execute(tquery, [nameF]) | 316 cur.execute(tquery, [nameF]) |
323 result = cur.fetchone() | 317 result = cur.fetchone() |
324 if result: | 318 if result: |
325 prev = calendar.timegm(result[0].utctimetuple()) | 319 prev = result[0] |
326 else: | 320 else: |
327 prev = 0 | 321 prev = datetime.fromtimestamp(0, utc); |
328 ttyrecdir = "/var/dgl/dgldir/ttyrec/{0}/{1}/".format(nameF, self.uname) | 322 ttyrecdir = "/var/dgl/dgldir/ttyrec/{0}/{1}/".format(nameF, self.uname) |
329 allfilekeys = [ (recnameToInt(f), f) for f in os.listdir(ttyrecdir) ] | 323 allfilekeys = [ (recnameToTS(f), f) for f in os.listdir(ttyrecdir) ] |
330 vfilekeys = [ e for e in allfilekeys if e[0] > prev ] | 324 vfilekeys = [ e for e in allfilekeys if e[0] > prev ] |
331 vfilekeys.sort(key=lambda e: e[0]) | 325 vfilekeys.sort(key=lambda e: e[0]) |
332 # Now determine stime and ttyrecs for each game | 326 # Now determine startt and ttyrecs for each game |
333 for i in range(0, len(itsEntries)): | 327 for i in range(0, len(itsEntries)): |
334 if i == 0: | 328 if i == 0: |
335 lowlim = prev | 329 lowlim = prev |
336 else: | 330 else: |
337 lowlim = itsEntries[i-1]["etime"] | 331 lowlim = itsEntries[i-1]["endt"] |
338 hilim = itsEntries[i]["etime"] | 332 hilim = itsEntries[i]["endt"] |
339 recs = [ k[1] for k in vfilekeys if lowlim <= k[0] < hilim ] | 333 recs = [ k[1] for k in vfilekeys if lowlim <= k[0] < hilim ] |
340 itsEntries[i]["stime"] = recnameToInt(recs[0]) | 334 itsEntries[i]["startt"] = recnameToTS(recs[0]) |
341 itsEntries[i]["ttyrecs"] = recs | 335 itsEntries[i]["ttyrecs"] = recs |
342 # While we're at it, replace numeric timestamps with SQL timestamps. | |
343 itsEntries[i]["ststamp"] = datetime.fromtimestamp(itsEntries[i]["stime"], utc) | |
344 itsEntries[i]["etstamp"] = datetime.fromtimestamp(itsEntries[i]["etime"], utc) | |
345 cur.close() | 336 cur.close() |
346 conn.close() | 337 conn.close() |
347 def loadnew(self): | 338 def loadnew(self): |
348 conn = getconn() | 339 conn = getconn() |
349 if conn == None: | 340 if conn == None: |