Merge branch 'master' of git://git.assembla.com/fpdboz

This commit is contained in:
Matt Turnbull
2009-06-15 22:21:40 +01:00
28 changed files with 1695 additions and 861 deletions
+3
View File
@@ -485,6 +485,9 @@ class Config:
try: db['db-server'] = self.supported_databases[name].db_server
except: pass
try: db['db-type'] = self.supported_databases[name].db_type
except: pass
if string.lower(self.supported_databases[name].db_server) == 'mysql':
db['db-backend'] = 2
elif string.lower(self.supported_databases[name].db_server) == 'postgresql':
+32 -28
View File
@@ -27,6 +27,7 @@ Create and manage the database objects.
import sys
import traceback
from datetime import datetime, date, time, timedelta
import string
# pyGTK modules
@@ -37,15 +38,21 @@ import Card
class Database:
def __init__(self, c, db_name, game):
if c.supported_databases[db_name].db_server == 'postgresql':
# psycopg2 database module for posgres via DB-API
import psycopg2
db_params = c.get_db_parameters()
if (string.lower(db_params['db-server']) == 'postgresql' or
string.lower(db_params['db-server']) == 'postgres'):
import psycopg2 # posgres via DB-API
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
try:
self.connection = psycopg2.connect(host = c.supported_databases[db_name].db_ip,
user = c.supported_databases[db_name].db_user,
password = c.supported_databases[db_name].db_pass,
database = c.supported_databases[db_name].db_name)
if db_params['db-host'] == 'localhost' or db_params['db-host'] == '127.0.0.1':
self.connection = psycopg2.connect(database = db_params['db-databaseName'])
else:
self.connection = psycopg2.connect(host = db_params['db-host'],
user = db_params['db-user'],
password = db_params['db-password'],
database = db_params['db-databaseName'])
except:
print "Error opening database connection %s. See error log file." % (file)
traceback.print_exc(file=sys.stderr)
@@ -53,14 +60,13 @@ class Database:
sys.stdin.readline()
sys.exit()
elif c.supported_databases[db_name].db_server == 'mysql':
# mysql bindings
import MySQLdb
elif string.lower(db_params['db-server']) == 'mysql':
import MySQLdb # mysql bindings
try:
self.connection = MySQLdb.connect(host = c.supported_databases[db_name].db_ip,
user = c.supported_databases[db_name].db_user,
passwd = c.supported_databases[db_name].db_pass,
db = c.supported_databases[db_name].db_name)
self.connection = MySQLdb.connect(host = db_params['db-host'],
user = db_params['db-user'],
passwd = db_params['db-password'],
db = db_params['db-databaseName'])
cur_iso = self.connection.cursor()
cur_iso.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED')
cur_iso.close()
@@ -78,11 +84,10 @@ class Database:
print "press enter to continue"
sys.exit()
self.db_server = c.supported_databases[db_name].db_server
self.type = c.supported_databases[db_name].db_type
self.sql = SQL.Sql(game = game, type = self.type, db_server = self.db_server)
self.type = db_params['db-type']
self.sql = SQL.Sql(game = game, type = self.type)
self.connection.rollback()
# To add to config:
self.hud_style = 'T' # A=All-time
# S=Session
@@ -193,20 +198,20 @@ class Database:
# cards += "xx"
# else:
# cards += ranks[d['card' + str(i) + 'Value']] + d['card' +str(i) + 'Suit']
cv = "card%dValue" % i
cv = "card%dvalue" % i
if cv not in d or d[cv] == None:
break
elif d[cv] == 0:
cards += "xx"
else:
cs = "card%dSuit" % i
cs = "card%dsuit" % i
cards = "%s%s%s" % (cards, ranks[d[cv]], d[cs])
return cards
def get_action_from_hand(self, hand_no):
action = [ [], [], [], [], [] ]
c = self.connection.cursor()
c.execute(self.sql.query['get_action_from_hand'], (hand_no))
c.execute(self.sql.query['get_action_from_hand'], (hand_no, ))
for row in c.fetchall():
street = row[0]
act = row[1:]
@@ -217,7 +222,7 @@ class Database:
"""Returns a hash of winners:amount won, given a hand number."""
winners = {}
c = self.connection.cursor()
c.execute(self.sql.query['get_winners_from_hand'], (hand))
c.execute(self.sql.query['get_winners_from_hand'], (hand, ))
for row in c.fetchall():
winners[row[0]] = row[1]
return winners
@@ -299,7 +304,6 @@ class Database:
return stat_dict
def get_player_id(self, config, site, player_name):
print "site = %s, player name = %s" % (site, player_name)
c = self.connection.cursor()
c.execute(self.sql.query['get_player_id'], {'player': player_name, 'site': site})
row = c.fetchone()
@@ -329,12 +333,12 @@ if __name__=="__main__":
for p in stat_dict.keys():
print p, " ", stat_dict[p]
#print "nutOmatics stats:"
#stat_dict = db_connection.get_stats_from_hand(h, hero)
#for p in stat_dict.keys():
# print p, " ", stat_dict[p]
# print "nutOmatics stats:"
# stat_dict = db_connection.get_stats_from_hand(h, hero)
# for p in stat_dict.keys():
# print p, " ", stat_dict[p]
print "cards =", db_connection.get_cards(73525)
print "cards =", db_connection.get_cards(u'1')
db_connection.close_connection
print "press enter to continue"
+1 -1
View File
@@ -158,7 +158,7 @@ or None if we fail to get the info """
# 2008/11/10 3:58:52 ET
#TODO: Do conversion from GMT to ET
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
hand.starttime = time.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
return
def readPlayerStacks(self, hand):
+46 -1
View File
@@ -44,6 +44,7 @@ class Filters(threading.Thread):
self.games = {}
self.limits = {}
self.seats = {}
self.groups = {}
self.siteid = {}
self.heroes = {}
self.boxes = {}
@@ -52,6 +53,7 @@ class Filters(threading.Thread):
self.filterText = {'limitsall':'All', 'limitsnone':'None', 'limitsshow':'Show _Limits'
,'seatsbetween':'Between:', 'seatsand':'And:', 'seatsshow':'Show Number of _Players'
,'limitstitle':'Limits:', 'seatstitle':'Number of Players:'
,'groupstitle':'Grouping:', 'posnshow':'Show Position Stats:'
}
# For use in date ranges.
@@ -109,6 +111,15 @@ class Filters(threading.Thread):
self.fillSeatsFrame(vbox, self.display)
seatsFrame.add(vbox)
# Groups
groupsFrame = gtk.Frame()
groupsFrame.show()
vbox = gtk.VBox(False, 0)
self.sbGroups = {}
self.fillGroupsFrame(vbox, self.display)
groupsFrame.add(vbox)
# Date
dateFrame = gtk.Frame("Date:")
dateFrame.set_label_align(0.0, 0.0)
@@ -131,6 +142,7 @@ class Filters(threading.Thread):
self.mainVBox.add(gamesFrame)
self.mainVBox.add(limitsFrame)
self.mainVBox.add(seatsFrame)
self.mainVBox.add(groupsFrame)
self.mainVBox.add(dateFrame)
self.mainVBox.add(self.Button1)
self.mainVBox.add(self.Button2)
@@ -148,6 +160,8 @@ class Filters(threading.Thread):
limitsFrame.hide()
if "Seats" not in self.display or self.display["Seats"] == False:
seatsFrame.hide()
if "Groups" not in self.display or self.display["Groups"] == False:
groupsFrame.hide()
if "Dates" not in self.display or self.display["Dates"] == False:
dateFrame.hide()
if "Button1" not in self.display or self.display["Button1"] == False:
@@ -183,6 +197,9 @@ class Filters(threading.Thread):
self.seats['to'] = self.sbSeats['to'].get_value_as_int()
return self.seats
def getGroups(self):
return self.groups
def getDates(self):
return self.__get_dates()
@@ -274,6 +291,11 @@ class Filters(threading.Thread):
self.seats[seat] = w.get_active()
print "self.seats[%s] set to %s" %(seat, self.seats[seat])
def __set_group_select(self, w, group):
#print "__set_seat_select: seat =", seat, "active =", w.get_active()
self.groups[group] = w.get_active()
print "self.groups[%s] set to %s" %(group, self.groups[group])
def fillPlayerFrame(self, vbox):
for site in self.conf.get_supported_sites():
pathHBox = gtk.HBox(False, 0)
@@ -389,10 +411,33 @@ class Filters(threading.Thread):
self.sbSeats['show'] = cb
self.seats['show'] = False
self.sbSeats['from'] = sb1
self.sbSeats['to'] = sb2
def fillGroupsFrame(self, vbox, display):
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, False, 0)
lbl_title = gtk.Label(self.filterText['groupstitle'])
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
hbox.pack_start(lbl_title, expand=True, padding=3)
showb = gtk.Button(label="hide", stock=None, use_underline=True)
showb.set_alignment(xalign=1.0, yalign=0.5)
showb.connect('clicked', self.__toggle_box, 'groups')
hbox.pack_start(showb, expand=False, padding=1)
vbox1 = gtk.VBox(False, 0)
vbox.pack_start(vbox1, False, False, 0)
self.boxes['groups'] = vbox1
hbox = gtk.HBox(False, 0)
vbox1.pack_start(hbox, False, True, 0)
cb = gtk.CheckButton(self.filterText['posnshow'])
cb.connect('clicked', self.__set_group_select, 'posn')
hbox.pack_start(cb, False, False, 0)
self.sbGroups['posn'] = cb
self.groups['posn'] = False
def fillCardsFrame(self, vbox):
hbox1 = gtk.HBox(True,0)
hbox1.show()
+15 -3
View File
@@ -910,6 +910,7 @@ class FpdbSQLQueries:
,min(gt.bigBlind) AS minbigblind
,max(gt.bigBlind) AS maxbigblind
/*,<hcgametypeId> AS gtid*/
,<position> AS plposition
,count(1) AS n
,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip
,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr
@@ -963,12 +964,17 @@ class FpdbSQLQueries:
,gt.base
,gt.category
<groupbyseats>
,plposition
,upper(gt.limitType)
,s.name
order by hp.playerId
,gt.base
,gt.category
<orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
else concat('Z', <position>)
end
<orderbyhgameTypeId>
,maxbigblind desc
,upper(gt.limitType)
@@ -983,7 +989,8 @@ class FpdbSQLQueries:
,s.name
,min(gt.bigBlind) AS minbigblind
,max(gt.bigBlind) AS maxbigblind
/*,<hcgametypeId> AS gtid*/
/*,<hcgametypeId> AS gtid*/
,<position> AS plposition
,count(1) AS n
,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip
,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr
@@ -993,8 +1000,8 @@ class FpdbSQLQueries:
,case when sum(cast(hp.stealattemptchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.stealattempted as <signed>integer))/sum(cast(hp.stealattemptchance as <signed>integer))
end AS steals
,100.0*sum(cast(hp.street1Seen as <signed>integer))/count(1) AS saw_f
,100.0*sum(cast(hp.sawShowdown as <signed>integer))/count(1) AS sawsd
,100.0*sum(cast(hp.street1Seen as <signed>integer))/count(1) AS saw_f
,100.0*sum(cast(hp.sawShowdown as <signed>integer))/count(1) AS sawsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.sawShowdown as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS wtsdwsf
@@ -1037,12 +1044,17 @@ class FpdbSQLQueries:
,gt.base
,gt.category
<groupbyseats>
,plposition
,upper(gt.limitType)
,s.name
order by hp.playerId
,gt.base
,gt.category
<orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
else 'Z'||<position>
end
<orderbyhgameTypeId>
,maxbigblind desc
,upper(gt.limitType)
+1 -1
View File
@@ -124,7 +124,7 @@ follow : whether to tail -f the input"""
hand.handid = m.group('HID')
hand.tablename = m.group('TABLE')
hand.starttime = time.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
hand.maxseats = 8 # assume 8-max until we see otherwise
if m.group('TABLEATTRIBUTES'):
m2 = re.search("(deep )?(\d+)( max)?", m.group('TABLEATTRIBUTES'))
+53 -33
View File
@@ -76,27 +76,28 @@ class GuiPlayerStats (threading.Thread):
# ToDo: create popup to adjust column config
# columns to display, keys match column name returned by sql, values in tuple are:
# is column displayed, column heading, xalignment, formatting
self.columns = [ ("game", True, "Game", 0.0, "%s")
, ("hand", False, "Hand", 0.0, "%s") # true not allowed for this line
, ("n", True, "Hds", 1.0, "%d")
, ("avgseats", True, "Seats", 1.0, "%3.1f")
, ("vpip", True, "VPIP", 1.0, "%3.1f")
, ("pfr", True, "PFR", 1.0, "%3.1f")
, ("pf3", True, "PF3", 1.0, "%3.1f")
, ("steals", True, "Steals", 1.0, "%3.1f")
, ("saw_f", True, "Saw_F", 1.0, "%3.1f")
, ("sawsd", True, "SawSD", 1.0, "%3.1f")
, ("wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f")
, ("wmsd", True, "W$SD", 1.0, "%3.1f")
, ("flafq", True, "FlAFq", 1.0, "%3.1f")
, ("tuafq", True, "TuAFq", 1.0, "%3.1f")
, ("rvafq", True, "RvAFq", 1.0, "%3.1f")
, ("pofafq", False, "PoFAFq", 1.0, "%3.1f")
, ("net", True, "Net($)", 1.0, "%6.2f")
, ("bbper100", True, "bb/100", 1.0, "%4.2f")
, ("rake", True, "Rake($)", 1.0, "%6.2f")
, ("bb100xr", True, "bbxr/100", 1.0, "%4.2f")
, ("variance", True, "Variance", 1.0, "%5.2f")
self.columns = [ ["game", True, "Game", 0.0, "%s"]
, ["hand", False, "Hand", 0.0, "%s"] # true not allowed for this line
, ["plposition", False, "Posn", 1.0, "%s"] # true not allowed for this line (set in code)
, ["n", True, "Hds", 1.0, "%d"]
, ["avgseats", True, "Seats", 1.0, "%3.1f"]
, ["vpip", True, "VPIP", 1.0, "%3.1f"]
, ["pfr", True, "PFR", 1.0, "%3.1f"]
, ["pf3", True, "PF3", 1.0, "%3.1f"]
, ["steals", True, "Steals", 1.0, "%3.1f"]
, ["saw_f", True, "Saw_F", 1.0, "%3.1f"]
, ["sawsd", True, "SawSD", 1.0, "%3.1f"]
, ["wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f"]
, ["wmsd", True, "W$SD", 1.0, "%3.1f"]
, ["flafq", True, "FlAFq", 1.0, "%3.1f"]
, ["tuafq", True, "TuAFq", 1.0, "%3.1f"]
, ["rvafq", True, "RvAFq", 1.0, "%3.1f"]
, ["pofafq", False, "PoFAFq", 1.0, "%3.1f"]
, ["net", True, "Net($)", 1.0, "%6.2f"]
, ["bbper100", True, "bb/100", 1.0, "%4.2f"]
, ["rake", True, "Rake($)", 1.0, "%6.2f"]
, ["bb100xr", True, "bbxr/100", 1.0, "%4.2f"]
, ["variance", True, "Variance", 1.0, "%5.2f"]
]
# Detail filters: This holds the data used in the popup window, extra values are
@@ -136,7 +137,7 @@ class GuiPlayerStats (threading.Thread):
self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
# make sure Hand column is not displayed
[x for x in self.columns if x[0] == 'hand'][0][1] == False
[x for x in self.columns if x[0] == 'hand'][0][1] = False
def get_vbox(self):
"""returns the vbox of this thread"""
@@ -156,6 +157,7 @@ class GuiPlayerStats (threading.Thread):
siteids = self.filters.getSiteIds()
limits = self.filters.getLimits()
seats = self.filters.getSeats()
groups = self.filters.getGroups()
dates = self.filters.getDates()
sitenos = []
playerids = []
@@ -180,16 +182,16 @@ class GuiPlayerStats (threading.Thread):
print "No limits found"
return
self.createStatsTable(vbox, playerids, sitenos, limits, seats, dates)
self.createStatsTable(vbox, playerids, sitenos, limits, seats, groups, dates)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates):
def createStatsTable(self, vbox, playerids, sitenos, limits, seats, groups, dates):
starttime = time()
# Display summary table at top of page
# 3rd parameter passes extra flags, currently includes:
# holecards - whether to display card breakdown (True/False)
flags = [False]
self.addTable(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, dates)
self.addTable(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates)
# Separator
sep = gtk.HSeparator()
@@ -212,13 +214,13 @@ class GuiPlayerStats (threading.Thread):
# Detailed table
flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, dates)
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates)
self.db.db.commit()
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox):
def addTable(self, vbox, query, flags, playerids, sitenos, limits, seats, dates):
def addTable(self, vbox, query, flags, playerids, sitenos, limits, seats, groups, dates):
row = 0
sqlrow = 0
colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4
@@ -231,7 +233,7 @@ class GuiPlayerStats (threading.Thread):
self.stats_table.show()
tmp = self.sql.query[query]
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, dates)
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, groups, dates)
self.cursor.execute(tmp)
result = self.cursor.fetchall()
colnames = [desc[0].lower() for desc in self.cursor.description]
@@ -245,6 +247,8 @@ class GuiPlayerStats (threading.Thread):
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
vbox.pack_start(view, expand=False, padding=3)
textcell = gtk.CellRendererText()
textcell50 = gtk.CellRendererText()
textcell50.set_property('xalign', 0.5)
numcell = gtk.CellRendererText()
numcell.set_property('xalign', 1.0)
listcols = []
@@ -258,17 +262,18 @@ class GuiPlayerStats (threading.Thread):
listcols.append(gtk.TreeViewColumn(s))
view.append_column(listcols[col])
if column[colformat] == '%s':
if col == 1 and holecards:
if column[colxalign] == 0.0:
listcols[col].pack_start(textcell, expand=True)
listcols[col].add_attribute(textcell, 'text', col)
else:
listcols[col].pack_start(textcell, expand=True)
listcols[col].add_attribute(textcell, 'text', col)
listcols[col].pack_start(textcell50, expand=True)
listcols[col].add_attribute(textcell50, 'text', col)
listcols[col].set_expand(True)
else:
listcols[col].pack_start(numcell, expand=True)
listcols[col].add_attribute(numcell, 'text', col)
listcols[col].set_alignment(1.0)
listcols[col].set_expand(True)
#listcols[col].set_alignment(column[colxalign]) # no effect?
rows = len(result) # +1 for title row
@@ -281,6 +286,11 @@ class GuiPlayerStats (threading.Thread):
for col,column in enumerate(cols_to_show):
if column[colalias] in colnames:
value = result[sqlrow][colnames.index(column[colalias])]
if column[colalias] == 'plposition':
if value == 'B':
value = 'BB'
if value == 'S':
value = 'SB'
else:
if column[colalias] == 'game':
if holecards:
@@ -313,7 +323,7 @@ class GuiPlayerStats (threading.Thread):
#end def addTable(self, query, vars, playerids, sitenos, limits, seats):
def refineQuery(self, query, flags, playerids, sitenos, limits, seats, dates):
def refineQuery(self, query, flags, playerids, sitenos, limits, seats, groups, dates):
if not flags: holecards = False
else: holecards = flags[0]
@@ -376,6 +386,16 @@ class GuiPlayerStats (threading.Thread):
# Filter on dates
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
# Group by position?
if groups['posn']:
query = query.replace("<position>", 'hp.position')
# set flag in self.columns to show posn column
[x for x in self.columns if x[0] == 'plposition'][0][1] = True
else:
query = query.replace("<position>", "'1'")
# unset flag in self.columns to hide posn column
[x for x in self.columns if x[0] == 'plposition'][0][1] = False
#print "query =\n", query
return(query)
#end def refineQuery(self, query, playerids, sitenos, limits):
-1
View File
@@ -148,7 +148,6 @@ class HUD_main(object):
if new_hand_id == "": # blank line means quit
self.destroy()
break # this thread is not always killed immediately with gtk.main_quit()
# get basic info about the new hand from the db
# if there is a db error, complain, skip hand, and proceed
try:
+5 -6
View File
@@ -92,16 +92,14 @@ db: a connected fpdb_db object"""
# HandsActions - all actions for all players for all streets - self.actions
# BoardCards - Skip - no longer necessary?
# Hands - Summary information of hand indexed by handId - gameinfo
# self.tablename = tableName
# self.handid = siteHandNo
#hh['siteHandNo'] = self.handid
# gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
#
# self.starttime = handStart
# importTime DATETIME NOT NULL,
#
#hh['handStart'] = self.starttime
# seats TINYINT NOT NULL,
#
# self.maxseats = maxSeats
#hh['tableName'] = self.tablenam
#hh['maxSeats'] = self.maxseats
# boardcard1 smallint, /* 0=none, 1-13=2-Ah 14-26=2-Ad 27-39=2-Ac 40-52=2-As */
# boardcard2 smallint,
# boardcard3 smallint,
@@ -141,6 +139,7 @@ db: a connected fpdb_db object"""
# showdownPot INT, /* pot size at sd/street7 */
# comment TEXT,
# commentTs DATETIME
# handid = db.storeHand(hh)
# HandsPlayers - ? ... Do we fix winnings?
# Tourneys ?
# TourneysPlayers
+1 -1
View File
@@ -132,7 +132,7 @@ Otherwise, finish at eof...
self.processHand(handText)
numHands= len(handsList)
endtime = time.time()
print "Processed %d hands in %.3f seconds" % (numHands, endtime - starttime)
print "read %d hands in %.3f seconds" % (numHands, endtime - starttime)
if self.out_fh != sys.stdout:
self.out_fh.close()
+10 -5
View File
@@ -543,11 +543,16 @@ class Sql:
self.query['get_common_cards'] = """
select
card1Value, card1Suit,
card2Value, card2Suit,
card3Value, card3Suit,
card4Value, card4Suit,
card5Value, card5Suit
card1Value AS card1value,
card1Suit AS card1suit,
card2Value AS card2value,
card2Suit AS card2suit,
card3Value AS card3value,
card3Suit AS card3suit,
card4Value AS card4value,
card4Suit AS card4suit,
card5Value AS card5value,
card5Suit AS card5suit
from BoardCards
where handId = %s
"""
Regular → Executable
View File
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python
"""Discover_TableWindow.py
Inspects the currently open windows and finds those of interest to us--that is
poker table windows from supported sites. Returns a list
of Table_Window objects representing the windows found.
"""
# Copyright 2008 - 2009, Ray E. Barker
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import os
import sys
# pyGTK modules
import pygtk
import gtk
import gobject
# FreePokerTools modules
import Configuration
#if os.name == "posix":
# import XTables
#elif os.name == "nt":
# import WinTables
# Global used for figuring out the current game being played from the title
# The dict key is the fpdb name for the game
# The list is the names for those games used by the supported poker sites
# This is currently only used for HORSE, so it only needs to support those
# games on PokerStars and Full Tilt.
game_names = { #fpdb name Stars Name FTP Name
"holdem" : ("Hold\'em" , ),
"omahahilo" : ("Omaha H/L" , ),
"studhilo" : ("Stud H/L" , ),
"razz" : ("Razz" , ),
"studhi" : ("Stud" , "Stud Hi")
}
# A window title might have our table name + one of theses words/
# phrases. If it has this word in the title, it is not a table.
bad_words = ('History for table:', 'HUD:', 'Chat:')
# Here are the custom signals we define for allowing the 'client watcher'
# thread to communicate with the gui thread. Any time a poker client is
# is moved, resized, or closed on of these signals is emitted to the
# HUD main window.
gobject.signal_new("client_moved", gtk.Window,
gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,))
gobject.signal_new("client_resized", gtk.Window,
gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,))
gobject.signal_new("client_destroyed", gtk.Window,
gobject.SIGNAL_RUN_LAST,
gobject.TYPE_NONE,
(gobject.TYPE_PYOBJECT,))
# Each TableWindow object must have the following attributes correctly populated:
# tw.name = the table name from the title bar, which must to match the table name
# from the corresponding hand history.
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# format that the system presents it. This is Xid in Xwindows and
# hwnd in Microsoft Windows.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
class Table_Window(object):
def __init__(self, table_name = None, tournament = None, table_number = None):
if table_name != None:
search_string = table_name
self.name = table_name
self.tournament = None
self.table = None
elif tournament != None and table_number != None:
print "tournament %s, table %s" % (tournament, table_number)
self.tournament = int(tournament)
self.table = int(table_number)
self.name = "%s - %s" % (self.tournament, self.table)
search_string = "%s.+Table\s%s" % (tournament, table_number)
else:
return None
self.find_table_parameters(search_string)
def __str__(self):
# __str__ method for testing
temp = 'TableWindow object\n'
temp = temp + " name = %s\n site = %s\n number = %s\n title = %s\n" % (self.name, self.site, self.number, self.title)
# temp = temp + " game = %s\n structure = %s\n max = %s\n" % (self.game, self.structure, self.max)
temp = temp + " width = %d\n height = %d\n x = %d\n y = %d\n" % (self.width, self.height, self.x, self.y)
if getattr(self, 'tournament', 0):
temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table)
return temp
def get_game(self):
title = self.get_window_title()
print title
for game, names in game_names.iteritems():
for name in names:
if name in title:
return game
return None
def check_geometry(self):
new_geo = self.get_geometry()
if new_geo == None: # window destroyed
return "client_destroyed"
elif self.x != new_geo['x'] or self.y != new_geo['y']: # window moved
self.x = new_geo['x']
self.y = new_geo['y']
return "client_moved"
elif self.width != new_geo['width'] or self.height != new_geo['height']: # window resized
self.width = new_geo['width']
self.height = new_geo['height']
return "client_resized"
else: return False # window not changed
def check_bad_words(self, title):
for word in bad_words:
if word in title: return True
return False
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python
"""Tables_Demo.py
Main program module to test/demo the Tables subclasses.
"""
# Copyright 2008 - 2009, Ray E. Barker
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import sys
import os
import re
# pyGTK modules
import pygtk
import gtk
import gobject
# fpdb/free poker tools modules
# get the correct module for the current os
if os.name == 'posix':
import XTables as Tables
elif os.name == 'nt':
import WinTables as Tables
# Main function used for testing
if __name__=="__main__":
# c = Configuration.Config()
class fake_hud(object):
def __init__(self, table, dx = 100, dy = 100):
self.table = table
self.dx = dx
self.dy = dy
self.main_window = gtk.Window()
self.main_window.connect("destroy", self.client_destroyed)
self.label = gtk.Label('Fake Fake Fake Fake\nFake\nFake\nFake')
self.main_window.add(self.label)
self.main_window.set_title("Fake HUD Main Window")
self.main_window.move(table.x + dx, table.y + dy)
self.main_window.show_all()
table.topify(self)
self.main_window.connect("client_moved", self.client_moved)
self.main_window.connect("client_resized", self.client_resized)
self.main_window.connect("client_destroyed", self.client_destroyed)
def client_moved(self, widget, hud):
self.main_window.move(self.table.x + self.dx, self.table.y + self.dy)
def client_resized(self, *args):
print "client resized"
def client_destroyed(self, *args): # call back for terminating the main eventloop
gtk.main_quit()
def check_on_table(table, hud):
result = table.check_geometry()
if result != False:
hud.main_window.emit(result, hud)
return True
print "enter table name to find: ",
table_name = sys.stdin.readline()
if "," in table_name: # tournament
print "tournament"
(tour_no, tab_no) = table_name.split(",", 1)
tour_no = tour_no.rstrip()
tab_no = tab_no.rstrip()
table = Tables.Table(tournament = tour_no, table_number = tab_no)
else: # not a tournament
print "cash game"
table_name = table_name.rstrip()
table = Tables.Table(table_name = table_name)
print "table =", table
print "game =", table.get_game()
fake = fake_hud(table)
gobject.timeout_add(100, check_on_table, table, fake)
gtk.main()
+143
View File
@@ -0,0 +1,143 @@
#!/usr/bin/env python
"""WinTables.py
Routines for detecting and handling poker client windows for MS Windows.
"""
# Copyright 2008 - 2009, Ray E. Barker
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import re
# pyGTK modules
import pygtk
import gtk
# Other Library modules
import win32gui
import win32process
import win32api
import win32con
import win32security
# FreePokerTools modules
from TableWindow import Table_Window
# We don't know the border width or title bar height
# so we guess here. We can probably get these from a windows call.
b_width = 3
tb_height = 29
class Table(Table_Window):
def find_table_parameters(self, search_string):
"""Finds poker client window with the given table name."""
titles = {}
win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles:
if re.search(search_string, titles[hwnd]):
if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window
if 'HUD:' in titles[hwnd]: continue # FPDB HUD window
if 'Chat:' in titles[hwnd]: continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
self.window = hwnd
break
if self.window == None:
print "Window %s not found. Skipping." % search_string
return None
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
print "x = %s y = %s width = %s height = %s" % (x, y, width, height)
self.x = int(x) + b_width
self.y = int(y) + tb_height
self.height = int(height) - b_width - tb_height
self.width = int(width) - 2*b_width
self.exe = self.get_nt_exe(hwnd)
self.title = titles[hwnd]
self.site = ""
self.hud = None
self.number = gtk.gdk.window_foreign_new(long(self.window))
def get_geometry(self):
if not win32gui.IsWindow(self.window): # window closed
return None
try:
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
return {'x' : int(x) + b_width,
'y' : int(y) + tb_height,
'width' : int(height) - b_width - tb_height,
'height' : int(width) - 2*b_width
}
except:
return None
def get_window_title(self):
return win32gui.GetWindowText(self.window)
def get_nt_exe(self, hwnd):
"""Finds the name of the executable that the given window handle belongs to."""
# Request privileges to enable "debug process", so we can later use PROCESS_VM_READ, retardedly required to GetModuleFileNameEx()
priv_flags = win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
hToken = win32security.OpenProcessToken (win32api.GetCurrentProcess(), priv_flags)
# enable "debug process"
privilege_id = win32security.LookupPrivilegeValue (None, win32security.SE_DEBUG_NAME)
old_privs = win32security.AdjustTokenPrivileges (hToken, 0, [(privilege_id, win32security.SE_PRIVILEGE_ENABLED)])
# Open the process, and query it's filename
processid = win32process.GetWindowThreadProcessId(hwnd)
pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1])
exename = win32process.GetModuleFileNameEx(pshandle, 0)
# clean up
win32api.CloseHandle(pshandle)
win32api.CloseHandle(hToken)
return exename
def win_enum_handler(hwnd, titles):
titles[hwnd] = win32gui.GetWindowText(hwnd)
def topify_window(hud, window):
"""Set the specified gtk window to stayontop in MS Windows."""
def windowEnumerationHandler(hwnd, resultList):
'''Callback for win32gui.EnumWindows() to generate list of window handles.'''
resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
unique_name = 'unique name for finding this window'
real_name = window.get_title()
window.set_title(unique_name)
tl_windows = []
win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
for w in tl_windows:
if w[1] == unique_name:
hud.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(long(hud.table.number))
hud.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0])
hud.main_window.gdkhandle.set_transient_for(hud.main_window.parentgdkhandle)
style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE)
style |= win32con.WS_CLIPCHILDREN
win32gui.SetWindowLong(hud.table.number, win32con.GWL_EXSTYLE, style)
break
window.set_title(real_name)
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python
"""Discover_Tables.py
Inspects the currently open windows and finds those of interest to us--that is
poker table windows from supported sites. Returns a list
of Table_Window objects representing the windows found.
"""
# Copyright 2008 - 2009, Ray E. Barker
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import re
# pyGTK modules
import pygtk
import gtk
# Other Library modules
import Xlib
import Xlib.display
# FreePokerTools modules
from TableWindow import Table_Window
# We might as well do this once and make them globals
disp = Xlib.display.Display()
root = disp.screen().root
class Table(Table_Window):
def find_table_parameters(self, search_string):
self.window = None
done_looping = False
for outside in root.query_tree().children:
for inside in outside.query_tree().children:
if done_looping: break
if inside.get_wm_name() and re.search(search_string, inside.get_wm_name()):
if self.check_bad_words(inside.get_wm_name()): continue
self.window = inside
self.parent = outside
done_looping = True
break
if self.window == None or self.parent == None:
print "Window %s not found. Skipping." % search_string
return None
my_geo = self.window.get_geometry()
pa_geo = self.parent.get_geometry()
self.x = pa_geo.x + my_geo.x
self.y = pa_geo.y + my_geo.y
self.width = my_geo.width
self.height = my_geo.height
self.exe = self.window.get_wm_class()[0]
self.title = self.window.get_wm_name()
self.site = ""
self.hud = None
window_string = str(self.window)
mo = re.match('Xlib\.display\.Window\(([\dxabcdef]+)', window_string)
if not mo:
print "Not matched"
self.gdk_handle = None
else:
self.number = int( mo.group(1), 0)
self.gdk_handle = gtk.gdk.window_foreign_new(int(self.number))
def get_geometry(self):
try:
my_geo = self.window.get_geometry()
pa_geo = self.parent.get_geometry()
return {'x' : pa_geo.x + my_geo.x,
'y' : pa_geo.y + my_geo.y,
'width' : my_geo.width,
'height' : my_geo.height
}
except:
return None
def get_window_title(self):
return self.window.get_wm_name()
def topify(self, hud):
hud.main_window.gdkhandle = gtk.gdk.window_foreign_new(hud.main_window.window.xid)
hud.main_window.gdkhandle.set_transient_for(self.gdk_handle)
+4 -4
View File
@@ -181,7 +181,7 @@ class fpdb:
def dia_load_profile(self, widget, data=None):
"""Dialogue to select a file to load a profile from"""
if self.obtain_global_lock():
if self.obtain_global_lock() == 0: # returns 0 if successful
try:
chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
@@ -201,7 +201,7 @@ class fpdb:
def dia_recreate_tables(self, widget, data=None):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
if self.obtain_global_lock():
if self.obtain_global_lock() in (0,2): # returns 0 if successful, 2 if Hands table does not exist
lock_released = False
try:
@@ -406,7 +406,7 @@ class fpdb:
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
return fpdb_simple.get_global_lock(self.fdb_lock)
return self.fdb_lock.get_global_lock()
#end def obtain_global_lock
def quit(self, widget, data):
@@ -455,7 +455,7 @@ class fpdb:
ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Positional Stats")
def tab_main_help(self, widget, data):
def tab_main_help(self, widget, data=None):
"""Displays a tab with the main fpdb help screen"""
#print "start of tab_main_help"
mh_tab=gtk.Label("""Welcome to Fpdb!
+405 -39
View File
@@ -17,6 +17,9 @@
import os
import re
import sys
from time import time, strftime
import fpdb_simple
import FpdbSQLQueries
@@ -29,6 +32,110 @@ class fpdb_db:
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import
# 1 - drop during bulk import
# db differences:
# - note that mysql automatically creates indexes on constrained columns when
# foreign keys are created, while postgres does not. Hence the much longer list
# of indexes is required for postgres.
# all primary keys are left on all the time
#
# table column drop_code
self.indexes = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # indexes for mysql (list index 2)
{'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
]
, [ # indexes for postgres (list index 3)
{'tab':'Boardcards', 'col':'handId', 'drop':0}
, {'tab':'Gametypes', 'col':'siteId', 'drop':0}
, {'tab':'Hands', 'col':'gametypeId', 'drop':0} # mct 22/3/09
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'HandsActions', 'col':'handsPlayerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
, {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
]
self.foreignKeys = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # foreign keys for mysql
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
, [ # foreign keys for postgres
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
]
# MySQL Notes:
# "FOREIGN KEY (handId) REFERENCES Hands(id)" - requires index on Hands.id
# - creates index handId on <thistable>.handId
# alter table t drop foreign key fk
# alter table t add foreign key (fkcol) references tab(rcol)
# alter table t add constraint c foreign key (fkcol) references tab(rcol)
# (fkcol is used for foreigh key name)
# mysql to list indexes:
# SELECT table_name, index_name, non_unique, column_name
# FROM INFORMATION_SCHEMA.STATISTICS
# WHERE table_name = 'tbl_name'
# AND table_schema = 'db_name'
# ORDER BY table_name, index_name, seq_in_index
#
# ALTER TABLE Tourneys ADD INDEX siteTourneyNo(siteTourneyNo)
# ALTER TABLE tab DROP INDEX idx
# mysql to list fks:
# SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
# FROM information_schema.KEY_COLUMN_USAGE
# WHERE REFERENCED_TABLE_SCHEMA = (your schema name here)
# AND REFERENCED_TABLE_NAME is not null
# ORDER BY TABLE_NAME, COLUMN_NAME;
# this may indicate missing object
# _mysql_exceptions.OperationalError: (1025, "Error on rename of '.\\fpdb\\hands' to '.\\fpdb\\#sql2-7f0-1b' (errno: 152)")
# PG notes:
# To add a foreign key constraint to a table:
# ALTER TABLE tab ADD CONSTRAINT c FOREIGN KEY (col) REFERENCES t2(col2) MATCH FULL;
# ALTER TABLE tab DROP CONSTRAINT zipchk
#
# Note: index names must be unique across a schema
# CREATE INDEX idx ON tab(col)
# DROP INDEX idx
#end def __init__
def do_connect(self, config=None):
@@ -65,27 +172,20 @@ class fpdb_db:
raise fpdb_simple.FpdbError("MySQL connection failed")
elif backend==self.PGSQL:
import psycopg2
import psycopg2.extensions
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
# If DB connection is made over TCP, then the variables
# host, user and password are required
print "host=%s user=%s pass=%s." % (host, user, password)
if self.host and self.user and self.password:
try:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
except:
raise fpdb_simple.FpdbError("PostgreSQL connection failed")
# For local domain-socket connections, only DB name is
# needed, and everything else is in fact undefined and/or
# flat out wrong
if self.host == "localhost" or self.host == "127.0.0.1":
self.db = psycopg2.connect(database = database)
else:
try:
self.db = psycopg2.connect(database = database)
except:
raise fpdb_simple.FpdbError("PostgreSQL connection failed")
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
else:
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor()
@@ -96,7 +196,7 @@ class fpdb_db:
try:
self.cursor.execute("SELECT * FROM Settings")
settings=self.cursor.fetchone()
if settings[0]!=119:
if settings[0]!=118:
print "outdated or too new database version - please recreate tables"
self.wrongDbVersion=True
except:# _mysql_exceptions.ProgrammingError:
@@ -201,14 +301,10 @@ class fpdb_db:
#end def get_db_info
def fillDefaultData(self):
self.cursor.execute("INSERT INTO Settings VALUES (119);")
self.cursor.execute("INSERT INTO Settings VALUES (118);")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Carbon', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'OnGame', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'UltimateBet', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Betfair', 'USD');")
self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#end def fillDefaultData
@@ -217,27 +313,297 @@ class fpdb_db:
self.drop_tables()
self.create_tables()
fpdb_simple.createAllIndexes(self)
self.createAllIndexes()
self.db.commit()
print "Finished recreating tables"
#end def recreate_tables
def getSqlPlayerIDs(names, site_id):
result = []
notfound = []
self.cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names))
tmp = dict(self.cursor.fetchall())
for n in names:
if n not in tmp:
notfound.append(n)
else:
result.append(tmp[n])
if notfound:
cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound))
cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound))
tmp = cursor.fetchall()
for n in tmp:
result.append(n[0])
def prepareBulkImport(self):
"""Drop some indexes/foreign keys to prepare for bulk import.
Currently keeping the standalone indexes as needed to import quickly"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for fk in self.foreignKeys[self.backend]:
if fk['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
self.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = self.cursor.fetchone()
#print "preparebulk: cons=", cons
if cons:
print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol']
try:
self.cursor.execute("alter table " + fk['fktab'] + " drop foreign key " + cons[0])
except:
pass
elif self.backend == self.PGSQL:
# DON'T FORGET TO RECREATE THEM!!
print "dropping pg fk", fk['fktab'], fk['fkcol']
try:
# try to lock table to see if index drop will work:
# hmmm, tested by commenting out rollback in grapher. lock seems to work but
# then drop still hangs :-( does work in some tests though??
# will leave code here for now pending further tests/enhancement ...
self.cursor.execute( "lock table %s in exclusive mode nowait" % (fk['fktab'],) )
#print "after lock, status:", self.cursor.statusmessage
#print "alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol'])
try:
self.cursor.execute("alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol']))
print "dropped pg fk pg fk %s_%s_fkey, continuing ..." % (fk['fktab'], fk['fkcol'])
except:
if "does not exist" not in str(sys.exc_value):
print "warning: drop pg fk %s_%s_fkey failed: %s, continuing ..." \
% (fk['fktab'], fk['fkcol'], str(sys.exc_value).rstrip('\n') )
except:
print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \
% (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n'))
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in self.indexes[self.backend]:
if idx['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
# apparently nowait is not implemented in mysql so this just hands if there are locks
# preventing the index drop :-(
self.cursor.execute( "alter table %s drop index %s", (idx['tab'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
# DON'T FORGET TO RECREATE THEM!!
print "dropping pg index ", idx['tab'], idx['col']
try:
# try to lock table to see if index drop will work:
self.cursor.execute( "lock table %s in exclusive mode nowait" % (idx['tab'],) )
#print "after lock, status:", self.cursor.statusmessage
try:
# table locked ok so index drop should work:
#print "drop index %s_%s_idx" % (idx['tab'],idx['col'])
self.cursor.execute( "drop index if exists %s_%s_idx" % (idx['tab'],idx['col']) )
#print "dropped pg index ", idx['tab'], idx['col']
except:
if "does not exist" not in str(sys.exc_value):
print "warning: drop index %s_%s_idx failed: %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
except:
print "warning: index %s_%s_idx not dropped %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
else:
print "Error: Only MySQL and Postgres supported so far"
return -1
#We proabably want to cache this
return result
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit() # seems to clear up errors if there were any in postgres
ptime = time() - stime
print "prepare import took", ptime, "seconds"
#end def prepareBulkImport
def afterBulkImport(self):
"""Re-create any dropped indexes/foreign keys after bulk import"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for fk in self.foreignKeys[self.backend]:
if fk['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
self.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = self.cursor.fetchone()
print "afterbulk: cons=", cons
if cons:
pass
else:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
self.cursor.execute("alter table " + fk['fktab'] + " add foreign key ("
+ fk['fkcol'] + ") references " + fk['rtab'] + "("
+ fk['rcol'] + ")")
except:
pass
elif self.backend == self.PGSQL:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
self.cursor.execute("alter table " + fk['fktab'] + " add constraint "
+ fk['fktab'] + '_' + fk['fkcol'] + '_fkey'
+ " foreign key (" + fk['fkcol']
+ ") references " + fk['rtab'] + "(" + fk['rcol'] + ")")
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in self.indexes[self.backend]:
if idx['drop'] == 1:
if self.backend == self.MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
self.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
# pass
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
self.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit() # seems to clear up errors if there were any in postgres
atime = time() - stime
print "after import took", atime, "seconds"
#end def afterBulkImport
def createAllIndexes(self):
"""Create new indexes"""
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for idx in self.indexes[self.backend]:
if self.backend == self.MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
self.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
self.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
#end def createAllIndexes
def dropAllIndexes(self):
"""Drop all standalone indexes (i.e. not including primary keys or foreign keys)
using list of indexes in indexes data structure"""
# maybe upgrade to use data dictionary?? (but take care to exclude PK and FK)
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow table/index operations to work
for idx in self.indexes[self.backend]:
if self.backend == self.MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
self.cursor.execute( "alter table %s drop index %s"
, (idx['tab'],idx['col']) )
except:
pass
elif self.backend == self.PGSQL:
print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
self.cursor.execute( "drop index %s_%s_idx"
% (idx['tab'],idx['col']) )
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
self.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes
def analyzeDB(self):
"""Do whatever the DB can offer to update index/table statistics"""
stime = time()
if self.backend == self.PGSQL:
self.db.set_isolation_level(0) # allow vacuum to work
try:
self.cursor.execute("vacuum analyze")
except:
print "Error during vacuum"
self.db.set_isolation_level(1) # go back to normal isolation level
self.db.commit()
atime = time() - stime
print "analyze took", atime, "seconds"
#end def analyzeDB
# Currently uses an exclusive lock on the Hands table as a global lock
# Return values are Unix style, 0 for success, positive integers for errors
# 1 = generic error
# 2 = hands table does not exist (error message is suppressed)
def get_global_lock(self):
if self.backend == self.MYSQL_INNODB:
try:
self.cursor.execute( "lock tables Hands write" )
except:
# Table 'fpdb.hands' doesn't exist
if str(sys.exc_value).find(".hands' doesn't exist") >= 0:
return(2)
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(1)
elif self.backend == self.PGSQL:
try:
self.cursor.execute( "lock table Hands in exclusive mode nowait" )
#print "... after lock table, status =", self.cursor.statusmessage
except:
# relation "hands" does not exist
if str(sys.exc_value).find('relation "hands" does not exist') >= 0:
return(2)
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(1)
return(0)
def storeHand(self, p):
#stores into table hands:
self.cursor.execute ("""INSERT INTO Hands
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats
,playersVpi, playersAtStreet1, playersAtStreet2
,playersAtStreet3, playersAtStreet4, playersAtShowdown
,street0Raises, street1Raises, street2Raises
,street3Raises, street4Raises, street1Pot
,street2Pot, street3Pot, street4Pot
,showdownPot
)
VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
,(p['siteHandNo'], gametype_id, p['handStart'], len(names), p['tableName'], datetime.datetime.today(), p['maxSeats']
,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2']
,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown']
,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises']
,hudCache['street3Raises'], hudCache['street4Raises'], hudCache['street1Pot']
,hudCache['street2Pot'], hudCache['street3Pot'], hudCache['street4Pot']
,hudCache['showdownPot']
)
)
#return getLastInsertId(backend, conn, cursor)
#end class fpdb_db
+26 -7
View File
@@ -44,6 +44,9 @@ except:
try:
import psycopg2
pgsqlLibFound=True
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
except:
pass
@@ -150,7 +153,9 @@ class Importer:
self.monitor = True
self.dirlist[site] = [dir] + [filter]
#print "addImportDirectory: checking files in", dir
for file in os.listdir(dir):
#print " adding file ", file
self.addImportFile(os.path.join(dir, file), site, filter)
else:
print "Warning: Attempted to add non-directory: '" + str(dir) + "' as an import directory"
@@ -162,7 +167,7 @@ class Importer:
if self.settings['dropIndexes'] == 'auto':
self.settings['dropIndexes'] = self.calculate_auto()
if self.settings['dropIndexes'] == 'drop':
fpdb_simple.prepareBulkImport(self.fdb)
self.fdb.prepareBulkImport()
totstored = 0
totdups = 0
totpartial = 0
@@ -177,8 +182,8 @@ class Importer:
toterrors += errors
tottime += ttime
if self.settings['dropIndexes'] == 'drop':
fpdb_simple.afterBulkImport(self.fdb)
fpdb_simple.analyzeDB(self.fdb)
self.fdb.afterBulkImport()
self.fdb.analyzeDB()
return (totstored, totdups, totpartial, toterrors, tottime)
# else: import threaded
@@ -203,14 +208,18 @@ class Importer:
#todo: make efficient - always checks for new file, should be able to use mtime of directory
# ^^ May not work on windows
#rulog = open('runUpdated.txt', 'a')
#rulog.writelines("runUpdated ... ")
for site in self.dirlist:
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
for file in self.filelist:
if os.path.exists(file):
stat_info = os.stat(file)
#rulog.writelines("path exists ")
try:
lastupdate = self.updated[file]
#rulog.writelines("lastupdate = %d, mtime = %d" % (lastupdate,stat_info.st_mtime))
if stat_info.st_mtime > lastupdate:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
self.updated[file] = time()
@@ -236,7 +245,8 @@ class Importer:
self.addToDirList = {}
self.removeFromFileList = {}
self.fdb.db.rollback()
#rulog.writelines(" finished\n")
#rulog.close()
# This is now an internal function that should not be called directly.
def import_file_dict(self, file, site, filter):
@@ -249,7 +259,7 @@ class Importer:
conv = None
# Load filter, process file, pass returned filename to import_fpdb_file
print "converting %s" % file
print "\nConverting %s" % file
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase)
hhdir = os.path.join(hhbase,site)
@@ -282,6 +292,7 @@ class Importer:
starttime = time()
last_read_hand = 0
loc = 0
#print "file =", file
if file == "stdin":
inputFile = sys.stdin
else:
@@ -292,10 +303,17 @@ class Importer:
return (0, 0, 0, 1, 0)
try:
loc = self.pos_in_file[file]
#size = os.path.getsize(file)
#print "loc =", loc, 'size =', size
except:
pass
# Read input file into class and close file
inputFile.seek(loc)
#tmplines = inputFile.readlines()
#if tmplines == None or tmplines == []:
# print "tmplines = ", tmplines
#else:
# print "tmplines[0] =", tmplines[0]
self.lines = fpdb_simple.removeTrailingEOL(inputFile.readlines())
self.pos_in_file[file] = inputFile.tell()
inputFile.close()
@@ -303,7 +321,8 @@ class Importer:
try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return.
firstline = self.lines[0]
except:
print "DEBUG: import_fpdb_file: failed on self.lines[0]: '%s' '%s' '%s' '%s' " %( file, site, self.lines, loc)
# just skip the debug message and return silently:
#print "DEBUG: import_fpdb_file: failed on self.lines[0]: '%s' '%s' '%s' '%s' " %( file, site, self.lines, loc)
return (0,0,0,1,0)
if firstline.find("Tournament Summary")!=-1:
@@ -348,6 +367,7 @@ class Importer:
if self.callHud:
#print "call to HUD here. handsId:",handsId
#pipe the Hands.id out to the HUD
print "sending hand to hud", handsId, "pipe =", self.caller.pipe_to_hud
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except fpdb_simple.DuplicateError:
duplicates += 1
@@ -364,7 +384,6 @@ class Importer:
except (fpdb_simple.FpdbError), fe:
errors += 1
self.printEmailErrorMessage(errors, file, hand)
self.fdb.db.rollback()
if self.settings['failOnError']:
+7 -6
View File
@@ -26,13 +26,14 @@ MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
fastStoreHudCache = False # set this to True to test the new storeHudCache routine
#fastStoreHudCache = False # set this to True to test the new storeHudCache routine
#
#saveActions = True # set this to False to avoid storing action data
# # Pros: speeds up imports
# # Cons: no action data is saved, so you need to keep the hand histories
# # variance not available on stats page
# # no graphs
saveActions = True # set this to False to avoid storing action data
# Pros: speeds up imports
# Cons: no action data is saved, so you need to keep the hand histories
# variance not available on stats page
# : No graphs
#stores a stud/razz hand into the database
def ring_stud(config, backend, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time
,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
+26 -353
View File
@@ -16,6 +16,10 @@
#agpl-3.0.txt in the docs folder of the package.
#This file contains simple functions for fpdb
#Aiming to eventually remove this module, functions will move to, eg:
#fpdb_db db create/re-create/management/etc
#Hands or related files for saving hands to db, etc
import datetime
import time
@@ -28,6 +32,7 @@ PS = 1
FTP = 2
# TODO: these constants are also used in fpdb_save_to_db and others, is there a way to do like C #define, and #include ?
# answer - yes. These are defined in fpdb_db so are accessible through that class.
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
@@ -35,359 +40,6 @@ SQLITE = 4
# config while trying out new hudcache mechanism
use_date_in_hudcache = True
# Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import
# 1 - drop during bulk import
# db differences:
# - note that mysql automatically creates indexes on constrained columns when
# foreign keys are created, while postgres does not. Hence the much longer list
# of indexes is required for postgres.
# all primary keys are left on all the time
#
# table column drop_code
indexes = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # indexes for mysql (list index 2)
{'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
]
, [ # indexes for postgres (list index 3)
{'tab':'Boardcards', 'col':'handId', 'drop':0}
, {'tab':'Gametypes', 'col':'siteId', 'drop':0}
, {'tab':'Hands', 'col':'gametypeId', 'drop':0} # mct 22/3/09
, {'tab':'Hands', 'col':'siteHandNo', 'drop':0}
, {'tab':'HandsActions', 'col':'handsPlayerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':1}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
, {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Players', 'col':'name', 'drop':0}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
]
foreignKeys = [
[ ] # no db with index 0
, [ ] # no db with index 1
, [ # foreign keys for mysql
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
, [ # foreign keys for postgres
{'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1}
, {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1}
, {'fktab':'HandsActions', 'fkcol':'handsPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1}
, {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0}
, {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1}
]
]
# MySQL Notes:
# "FOREIGN KEY (handId) REFERENCES Hands(id)" - requires index on Hands.id
# - creates index handId on <thistable>.handId
# alter table t drop foreign key fk
# alter table t add foreign key (fkcol) references tab(rcol)
# alter table t add constraint c foreign key (fkcol) references tab(rcol)
# (fkcol is used for foreigh key name)
# mysql to list indexes:
# SELECT table_name, index_name, non_unique, column_name
# FROM INFORMATION_SCHEMA.STATISTICS
# WHERE table_name = 'tbl_name'
# AND table_schema = 'db_name'
# ORDER BY table_name, index_name, seq_in_index
#
# ALTER TABLE Tourneys ADD INDEX siteTourneyNo(siteTourneyNo)
# ALTER TABLE tab DROP INDEX idx
# mysql to list fks:
# SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
# FROM information_schema.KEY_COLUMN_USAGE
# WHERE REFERENCED_TABLE_SCHEMA = (your schema name here)
# AND REFERENCED_TABLE_NAME is not null
# ORDER BY TABLE_NAME, COLUMN_NAME;
# this may indicate missing object
# _mysql_exceptions.OperationalError: (1025, "Error on rename of '.\\fpdb\\hands' to '.\\fpdb\\#sql2-7f0-1b' (errno: 152)")
# PG notes:
# To add a foreign key constraint to a table:
# ALTER TABLE tab ADD CONSTRAINT c FOREIGN KEY (col) REFERENCES t2(col2) MATCH FULL;
# ALTER TABLE tab DROP CONSTRAINT zipchk
#
# Note: index names must be unique across a schema
# CREATE INDEX idx ON tab(col)
# DROP INDEX idx
def prepareBulkImport(fdb):
"""Drop some indexes/foreign keys to prepare for bulk import.
Currently keeping the standalone indexes as needed to import quickly"""
# fdb is a fpdb_db object including backend, db, cursor, sql variables
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for fk in foreignKeys[fdb.backend]:
if fk['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
fdb.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = fdb.cursor.fetchone()
#print "preparebulk: cons=", cons
if cons:
print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " drop foreign key " + cons[0])
except:
pass
elif fdb.backend == PGSQL:
# DON'T FORGET TO RECREATE THEM!!
print "dropping pg fk", fk['fktab'], fk['fkcol']
try:
# try to lock table to see if index drop will work:
# hmmm, tested by commenting out rollback in grapher. lock seems to work but
# then drop still hangs :-( does work in some tests though??
# will leave code here for now pending further tests/enhancement ...
fdb.cursor.execute( "lock table %s in exclusive mode nowait" % (fk['fktab'],) )
#print "after lock, status:", fdb.cursor.statusmessage
#print "alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol'])
try:
fdb.cursor.execute("alter table %s drop constraint %s_%s_fkey" % (fk['fktab'], fk['fktab'], fk['fkcol']))
print "dropped pg fk pg fk %s_%s_fkey, continuing ..." % (fk['fktab'], fk['fkcol'])
except:
if "does not exist" not in str(sys.exc_value):
print "warning: drop pg fk %s_%s_fkey failed: %s, continuing ..." \
% (fk['fktab'], fk['fkcol'], str(sys.exc_value).rstrip('\n') )
except:
print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \
% (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n'))
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in indexes[fdb.backend]:
if idx['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
# apparently nowait is not implemented in mysql so this just hands if there are locks
# preventing the index drop :-(
fdb.cursor.execute( "alter table %s drop index %s", (idx['tab'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# DON'T FORGET TO RECREATE THEM!!
print "dropping pg index ", idx['tab'], idx['col']
try:
# try to lock table to see if index drop will work:
fdb.cursor.execute( "lock table %s in exclusive mode nowait" % (idx['tab'],) )
#print "after lock, status:", fdb.cursor.statusmessage
try:
# table locked ok so index drop should work:
#print "drop index %s_%s_idx" % (idx['tab'],idx['col'])
fdb.cursor.execute( "drop index if exists %s_%s_idx" % (idx['tab'],idx['col']) )
#print "dropped pg index ", idx['tab'], idx['col']
except:
if "does not exist" not in str(sys.exc_value):
print "warning: drop index %s_%s_idx failed: %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
except:
print "warning: index %s_%s_idx not dropped %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
else:
print "Error: Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit() # seems to clear up errors if there were any in postgres
#end def prepareBulkImport
def afterBulkImport(fdb):
"""Re-create any dropped indexes/foreign keys after bulk import"""
# fdb is a fpdb_db object including backend, db, cursor, sql variables
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for fk in foreignKeys[fdb.backend]:
if fk['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
fdb.cursor.execute("SELECT constraint_name " +
"FROM information_schema.KEY_COLUMN_USAGE " +
#"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb'
"WHERE 1=1 " +
"AND table_name = %s AND column_name = %s " +
"AND referenced_table_name = %s " +
"AND referenced_column_name = %s ",
(fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) )
cons = fdb.cursor.fetchone()
print "afterbulk: cons=", cons
if cons:
pass
else:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " add foreign key ("
+ fk['fkcol'] + ") references " + fk['rtab'] + "("
+ fk['rcol'] + ")")
except:
pass
elif fdb.backend == PGSQL:
print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol']
try:
fdb.cursor.execute("alter table " + fk['fktab'] + " add constraint "
+ fk['fktab'] + '_' + fk['fkcol'] + '_fkey'
+ " foreign key (" + fk['fkcol']
+ ") references " + fk['rtab'] + "(" + fk['rcol'] + ")")
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in indexes[fdb.backend]:
if idx['drop'] == 1:
if fdb.backend == MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# pass
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
fdb.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit() # seems to clear up errors if there were any in postgres
#end def afterBulkImport
def createAllIndexes(fdb):
"""Create new indexes"""
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for idx in indexes[fdb.backend]:
if fdb.backend == MYSQL_INNODB:
print "creating mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s add index %s(%s)"
, (idx['tab'],idx['col'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
# mod to use tab_col for index name?
print "creating pg index ", idx['tab'], idx['col']
try:
print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
fdb.cursor.execute( "create index %s_%s_idx on %s(%s)"
% (idx['tab'], idx['col'], idx['tab'], idx['col']) )
except:
print " ERROR! :-("
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def createAllIndexes
def dropAllIndexes(fdb):
"""Drop all standalone indexes (i.e. not including primary keys or foreign keys)
using list of indexes in indexes data structure"""
# maybe upgrade to use data dictionary?? (but take care to exclude PK and FK)
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow table/index operations to work
for idx in indexes[fdb.backend]:
if fdb.backend == MYSQL_INNODB:
print "dropping mysql index ", idx['tab'], idx['col']
try:
fdb.cursor.execute( "alter table %s drop index %s"
, (idx['tab'],idx['col']) )
except:
pass
elif fdb.backend == PGSQL:
print "dropping pg index ", idx['tab'], idx['col']
# mod to use tab_col for index name?
try:
fdb.cursor.execute( "drop index %s_%s_idx"
% (idx['tab'],idx['col']) )
except:
pass
else:
print "Only MySQL and Postgres supported so far"
return -1
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes
def analyzeDB(fdb):
"""Do whatever the DB can offer to update index/table statistics"""
if fdb.backend == PGSQL:
fdb.db.set_isolation_level(0) # allow vacuum to work
try:
fdb.cursor.execute("vacuum analyze")
except:
print "Error during vacuum"
fdb.db.set_isolation_level(1) # go back to normal isolation level
fdb.db.commit()
#end def analyzeDB
def get_global_lock(fdb):
if fdb.backend == MYSQL_INNODB:
try:
fdb.cursor.execute( "lock tables Hands write" )
except:
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(False)
elif fdb.backend == PGSQL:
try:
fdb.cursor.execute( "lock table Hands in exclusive mode nowait" )
#print "... after lock table, status =", fdb.cursor.statusmessage
except:
print "Error! failed to obtain global lock. Close all programs accessing " \
+ "database (including fpdb) and try again (%s)." \
% ( str(sys.exc_value).rstrip('\n'), )
return(False)
return(True)
class DuplicateError(Exception):
def __init__(self, value):
self.value = value
@@ -1390,6 +1042,27 @@ def recognisePlayerIDs(cursor, names, site_id):
#end def recognisePlayerIDs
# Here's a version that would work if it wasn't for the fact that it needs to have the output in the same order as input
# this version could also be improved upon using list comprehensions, etc
#def recognisePlayerIDs(cursor, names, site_id):
# result = []
# notfound = []
# cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names))
# tmp = dict(cursor.fetchall())
# for n in names:
# if n not in tmp:
# notfound.append(n)
# else:
# result.append(tmp[n])
# if notfound:
# cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound))
# cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound))
# tmp = cursor.fetchall()
# for n in tmp:
# result.append(n[0])
#
# return result
#recognises the name in the given line and returns its array position in the given array
def recognisePlayerNo(line, names, atype):
+8 -20
View File
@@ -16,24 +16,12 @@ def testPokerStarsHHDate():
datetime.datetime(2008,9,7,11,23,14))
)
def testFullTiltHHDate():
sitngo1 = "Full Tilt Poker Game #10311865543: $1 + $0.25 Sit & Go (78057629), Table 1 - 25/50 - No Limit Hold'em - 0:07:45 ET - 2009/01/29"
cash1 = "Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09"
cash2 = "Full Tilt Poker Game #9468383505: Table Bike (deep 6) - $0.05/$0.10 - No Limit Hold'em - 5:09:36 ET - 2008/12/13"
result = fpdb_simple.parseHandStartTime(sitngo1,"ftp")
assert result==datetime.datetime(2009,1,29,05,07,45)
result = fpdb_simple.parseHandStartTime(cash1,"ftp")
assert result==datetime.datetime(2008,12,9,14,40,20)
result = fpdb_simple.parseHandStartTime(cash2,"ftp")
assert result==datetime.datetime(2008,12,13,10,9,36)
def testTableDetection():
result = Tables.clean_title("French (deep)")
assert result == "French"
result = Tables.clean_title("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx")
assert result == "French"
for (header, site, result) in tuples:
yield checkDateParse, header, site, result
#def testTableDetection():
# result = Tables.clean_title("French (deep)")
# assert result == "French"
# result = Tables.clean_title("French (deep) - $0.25/$0.50 - No Limit Hold'em - Logged In As xxxx")
# assert result == "French"
#
# for (header, site, result) in tuples:
# yield checkDateParse, header, site, result