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

This commit is contained in:
eblade 2009-07-25 13:08:21 -04:00
commit 308cfb1e56
36 changed files with 3862 additions and 3045 deletions

View File

@ -34,12 +34,12 @@ class Betfair(HandHistoryConverter):
re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)") re_PlayerInfo = re.compile("Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*)\s\(\s(\$(?P<CASH>[.0-9]+)) \)")
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]") re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True): def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
follow : whether to tail -f the input""" follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Betfair", follow=follow) # Call super class init. HandHistoryConverter.__init__(self, in_path, out_path, sitename="Betfair", follow=follow, index) # Call super class init.
logging.info("Initialising Betfair converter class") logging.info("Initialising Betfair converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"

View File

@ -57,6 +57,21 @@ def fourStartCards(value1, suit1, value2, suit2, value3, suit3, value4, suit4):
#AAKKs #AAKKs
#AAKKr #AAKKr
# Is probably what we are looking for # Is probably what we are looking for
# mct:
# my maths says there are 4 classes of suitedness
# SSSS SSSx SSxy SSHH
# encode them as follows:
# SSSS (K, J, 6, 3)
# - 13C4 = 715 possibilities
# SSSx (K, J, 6),(3)
# - 13C3 * 13 = 3718 possibilities
# SSxy (K, J),(6),(3)
# - 13C2 * 13*13 = 13182 possibilities
# SSHH (K, J),(6, 3)
# - 13C2 * 13C2 = 6084 possibilities
# Needless to say they won't fit on a 13x13 grid.
# The actual number of hands in each class is far greater
return(0) return(0)
def cardFromValueSuit(value, suit): def cardFromValueSuit(value, suit):
@ -70,7 +85,7 @@ def cardFromValueSuit(value, suit):
def valueSuitFromCard(card): def valueSuitFromCard(card):
""" Function to convert a card stored in the database (int 0-52) into value """ Function to convert a card stored in the database (int 0-52) into value
and suit like 9s, 4c etc """ and suit like 9s, 4c etc """
if card < 0 or card > 52: if card < 0 or card > 52 or not card:
return('') return('')
else: else:
return( ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', 'Ah' return( ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', 'Ah'
@ -80,4 +95,7 @@ def valueSuitFromCard(card):
][card] ) ][card] )
if __name__ == '__main__':
for i in xrange(1, 14):
print "card %2d = %s card %2d = %s card %2d = %s card %2d = %s" % \
(i, valueSuitFromCard(i), i+13, valueSuitFromCard(i+13), i+26, valueSuitFromCard(i+26), i+39, valueSuitFromCard(i+39))

View File

@ -88,12 +88,32 @@ class Site:
self.font_size = node.getAttribute("font_size") self.font_size = node.getAttribute("font_size")
self.use_frames = node.getAttribute("use_frames") self.use_frames = node.getAttribute("use_frames")
self.enabled = fix_tf(node.getAttribute("enabled"), default = True) self.enabled = fix_tf(node.getAttribute("enabled"), default = True)
self.xpad = node.getAttribute("xpad")
self.ypad = node.getAttribute("ypad")
self.layout = {} self.layout = {}
for layout_node in node.getElementsByTagName('layout'): for layout_node in node.getElementsByTagName('layout'):
lo = Layout(layout_node) lo = Layout(layout_node)
self.layout[lo.max] = lo self.layout[lo.max] = lo
# Site defaults
if self.xpad == "": self.xpad = 1
else: self.xpad = int(self.xpad)
if self.ypad == "": self.ypad = 0
else: self.ypad = int(self.ypad)
if self.font_size == "": self.font_size = 7
else: self.font_size = int(self.font_size)
if self.hudopacity == "": self.hudopacity = 1.0
else: self.hudopacity = float(self.hudopacity)
if self.use_frames == "": self.use_frames = False
if self.font == "": self.font = "Sans"
if self.hudbgcolor == "": self.hudbgcolor = "000000"
if self.hudfgcolor == "": self.hudfgcolor = "FFFFFF"
def __str__(self): def __str__(self):
temp = "Site = " + self.site_name + "\n" temp = "Site = " + self.site_name + "\n"
for key in dir(self): for key in dir(self):
@ -119,9 +139,16 @@ class Stat:
class Game: class Game:
def __init__(self, node): def __init__(self, node):
self.game_name = node.getAttribute("game_name") self.game_name = node.getAttribute("game_name")
self.db = node.getAttribute("db")
self.rows = int( node.getAttribute("rows") ) self.rows = int( node.getAttribute("rows") )
self.cols = int( node.getAttribute("cols") ) self.cols = int( node.getAttribute("cols") )
self.xpad = node.getAttribute("xpad")
self.ypad = node.getAttribute("ypad")
# Defaults
if self.xpad == "": self.xpad = 1
else: self.xpad = int(self.xpad)
if self.ypad == "": self.ypad = 0
else: self.ypad = int(self.ypad)
aux_text = node.getAttribute("aux") aux_text = node.getAttribute("aux")
aux_list = aux_text.split(',') aux_list = aux_text.split(',')
@ -146,9 +173,10 @@ class Game:
def __str__(self): def __str__(self):
temp = "Game = " + self.game_name + "\n" temp = "Game = " + self.game_name + "\n"
temp = temp + " db = %s\n" % self.db
temp = temp + " rows = %d\n" % self.rows temp = temp + " rows = %d\n" % self.rows
temp = temp + " cols = %d\n" % self.cols temp = temp + " cols = %d\n" % self.cols
temp = temp + " xpad = %d\n" % self.xpad
temp = temp + " ypad = %d\n" % self.ypad
temp = temp + " aux = %s\n" % self.aux temp = temp + " aux = %s\n" % self.aux
for stat in self.stats.keys(): for stat in self.stats.keys():
@ -250,6 +278,7 @@ class Config:
self.default_config_path = self.get_default_config_path() self.default_config_path = self.get_default_config_path()
if file != None: # configuration file path has been passed if file != None: # configuration file path has been passed
file = os.path.expanduser(file)
if not os.path.exists(file): if not os.path.exists(file):
print "Configuration file %s not found. Using defaults." % (file) print "Configuration file %s not found. Using defaults." % (file)
sys.stderr.write("Configuration file %s not found. Using defaults." % (file)) sys.stderr.write("Configuration file %s not found. Using defaults." % (file))
@ -492,6 +521,8 @@ class Config:
db['db-backend'] = 2 db['db-backend'] = 2
elif string.lower(self.supported_databases[name].db_server) == 'postgresql': elif string.lower(self.supported_databases[name].db_server) == 'postgresql':
db['db-backend'] = 3 db['db-backend'] = 3
elif string.lower(self.supported_databases[name].db_server) == 'sqlite':
db['db-backend'] = 4
else: db['db-backend'] = None # this is big trouble else: db['db-backend'] = None # this is big trouble
return db return db
@ -630,6 +661,8 @@ class Config:
parms["font"] = self.supported_sites[site].font parms["font"] = self.supported_sites[site].font
parms["font_size"] = self.supported_sites[site].font_size parms["font_size"] = self.supported_sites[site].font_size
parms["enabled"] = self.supported_sites[site].enabled parms["enabled"] = self.supported_sites[site].enabled
parms["xpad"] = self.supported_sites[site].xpad
parms["ypad"] = self.supported_sites[site].ypad
return parms return parms
def set_site_parameters(self, site_name, converter = None, decoder = None, def set_site_parameters(self, site_name, converter = None, decoder = None,
@ -680,9 +713,10 @@ class Config:
param = {} param = {}
if self.supported_games.has_key(name): if self.supported_games.has_key(name):
param['game_name'] = self.supported_games[name].game_name param['game_name'] = self.supported_games[name].game_name
param['db'] = self.supported_games[name].db
param['rows'] = self.supported_games[name].rows param['rows'] = self.supported_games[name].rows
param['cols'] = self.supported_games[name].cols param['cols'] = self.supported_games[name].cols
param['xpad'] = self.supported_games[name].xpad
param['ypad'] = self.supported_games[name].ypad
param['aux'] = self.supported_games[name].aux param['aux'] = self.supported_games[name].aux
return param return param

View File

@ -27,66 +27,47 @@ Create and manage the database objects.
import sys import sys
import traceback import traceback
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
from time import time, strftime
import string import string
# pyGTK modules # pyGTK modules
# FreePokerTools modules # FreePokerTools modules
import fpdb_db
import fpdb_simple
import Configuration import Configuration
import SQL import SQL
import Card import Card
class Database: class Database:
def __init__(self, c, db_name, game):
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
def __init__(self, c, db_name = None, game = None, sql = None): # db_name and game not used any more
print "\ncreating Database instance, sql =", sql
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.do_connect(c)
self.connection = self.fdb.db
db_params = c.get_db_parameters() db_params = c.get_db_parameters()
if (string.lower(db_params['db-server']) == 'postgresql' or self.import_options = c.get_import_parameters()
string.lower(db_params['db-server']) == 'postgres'):
import psycopg2 # posgres via DB-API
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
try:
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)
print "press enter to continue"
sys.stdin.readline()
sys.exit()
elif string.lower(db_params['db-server']) == 'mysql':
import MySQLdb # mysql bindings
try:
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()
except:
print "Error opening database connection %s. See error log file." % (file)
traceback.print_exc(file=sys.stderr)
print "press enter to continue"
sys.stdin.readline()
sys.exit()
else:
print "Database = %s not recognized." % (c.supported_databases[db_name].db_server)
sys.stderr.write("Database not recognized, exiting.\n")
print "press enter to continue"
sys.exit()
self.type = db_params['db-type'] self.type = db_params['db-type']
self.sql = SQL.Sql(game = game, type = self.type) self.backend = db_params['db-backend']
self.connection.rollback() self.db_server = db_params['db-server']
if self.backend == self.PGSQL:
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE
#ISOLATION_LEVEL_AUTOCOMMIT = 0
#ISOLATION_LEVEL_READ_COMMITTED = 1
#ISOLATION_LEVEL_SERIALIZABLE = 2
# where possible avoid creating new SQL instance by using the global one passed in
if sql == None:
self.sql = SQL.Sql(type = self.type, db_server = db_params['db-server'])
else:
self.sql = sql
# To add to config: # To add to config:
self.hud_style = 'T' # A=All-time self.hud_style = 'T' # A=All-time
@ -95,31 +76,78 @@ class Database:
# Future values may also include: # Future values may also include:
# H=Hands (last n hands) # H=Hands (last n hands)
self.hud_hands = 1000 # Max number of hands from each player to use for hud stats self.hud_hands = 1000 # Max number of hands from each player to use for hud stats
self.hud_days = 90 # Max number of days from each player to use for hud stats self.hud_days = 30 # Max number of days from each player to use for hud stats
self.hud_session_gap = 30 # Gap (minutes) between hands that indicates a change of session self.hud_session_gap = 30 # Gap (minutes) between hands that indicates a change of session
# (hands every 2 mins for 1 hour = one session, if followed # (hands every 2 mins for 1 hour = one session, if followed
# by a 40 minute gap and then more hands on same table that is # by a 40 minute gap and then more hands on same table that is
# a new session) # a new session)
cur = self.connection.cursor() self.cursor = self.fdb.cursor
if self.fdb.wrongDbVersion == False:
# self.hand_1day_ago used to fetch stats for current session (i.e. if hud_style = 'S')
self.hand_1day_ago = 0 self.hand_1day_ago = 0
cur.execute(self.sql.query['get_hand_1day_ago']) self.cursor.execute(self.sql.query['get_hand_1day_ago'])
row = cur.fetchone() row = self.cursor.fetchone()
if row and row[0]: if row and row[0]:
self.hand_1day_ago = row[0] self.hand_1day_ago = row[0]
#print "hand 1day ago =", self.hand_1day_ago #print "hand 1day ago =", self.hand_1day_ago
# self.date_ndays_ago used if hud_style = 'T'
d = timedelta(days=self.hud_days) d = timedelta(days=self.hud_days)
now = datetime.utcnow() - d now = datetime.utcnow() - d
self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day) self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day)
self.hand_nhands_ago = 0 # todo # self.hand_nhands_ago is used for fetching stats for last n hands (hud_style = 'H')
#cur.execute(self.sql.query['get_table_name'], (hand_id, )) # This option not used yet
#row = cur.fetchone() self.hand_nhands_ago = 0
# should use aggregated version of query if appropriate
self.cursor.execute(self.sql.query['get_hand_nhands_ago'], (self.hud_hands,self.hud_hands))
row = self.cursor.fetchone()
if row and row[0]:
self.hand_nhands_ago = row[0]
print "hand n hands ago =", self.hand_nhands_ago
#self.cursor.execute(self.sql.query['get_table_name'], (hand_id, ))
#row = self.cursor.fetchone()
else:
print "Bailing on DB query, not sure it exists yet"
self.saveActions = False if self.import_options['saveActions'] == False else True
self.connection.rollback() # make sure any locks taken so far are released
# could be used by hud to change hud style
def set_hud_style(self, style):
self.hud_style = style
def do_connect(self, c):
self.fdb.do_connect(c)
def commit(self):
self.fdb.db.commit()
def rollback(self):
self.fdb.db.rollback()
def get_cursor(self):
return self.connection.cursor()
def close_connection(self): def close_connection(self):
self.connection.close() self.connection.close()
def disconnect(self, due_to_error=False):
"""Disconnects the DB (rolls back if param is true, otherwise commits"""
self.fdb.disconnect(due_to_error)
def reconnect(self, due_to_error=False):
"""Reconnects the DB"""
self.fdb.reconnect(due_to_error=False)
def get_backend_name(self):
"""Reconnects the DB"""
return self.fdb.get_backend_name()
def get_table_name(self, hand_id): def get_table_name(self, hand_id):
c = self.connection.cursor() c = self.connection.cursor()
c.execute(self.sql.query['get_table_name'], (hand_id, )) c.execute(self.sql.query['get_table_name'], (hand_id, ))
@ -156,21 +184,13 @@ class Database:
def get_cards(self, hand): def get_cards(self, hand):
"""Get and return the cards for each player in the hand.""" """Get and return the cards for each player in the hand."""
cards = {} # dict of cards, the key is the seat number example: {1: 'AcQd9hTs5d'} cards = {} # dict of cards, the key is the seat number,
# the value is a tuple of the players cards
# example: {1: (0, 0, 20, 21, 22, 0 , 0)}
c = self.connection.cursor() c = self.connection.cursor()
c.execute(self.sql.query['get_cards'], [hand]) c.execute(self.sql.query['get_cards'], [hand])
colnames = [desc[0] for desc in c.description]
cardnames = ['card1', 'card2', 'card3', 'card4', 'card5', 'card6', 'card7']
for row in c.fetchall(): for row in c.fetchall():
cs = ['', '', '', '', '', '', ''] cards[row[0]] = row[1:]
seat = -1
for col,name in enumerate(colnames):
if name in cardnames:
cs[cardnames.index(name)] = Card.valueSuitFromCard(row[col])
elif name == 'seat_number':
seat = row[col]
if seat != -1:
cards[seat] = ''.join(cs)
return cards return cards
def get_common_cards(self, hand): def get_common_cards(self, hand):
@ -178,12 +198,8 @@ class Database:
cards = {} cards = {}
c = self.connection.cursor() c = self.connection.cursor()
c.execute(self.sql.query['get_common_cards'], [hand]) c.execute(self.sql.query['get_common_cards'], [hand])
colnames = [desc[0] for desc in c.description] # row = c.fetchone()
for row in c.fetchall(): cards['common'] = c.fetchone()
s_dict = {}
for name, val in zip(colnames, row):
s_dict[name] = val
cards['common'] = (self.convert_cards(s_dict))
return cards return cards
def convert_cards(self, d): def convert_cards(self, d):
@ -312,6 +328,159 @@ class Database:
else: else:
return None return None
def get_last_insert_id(self):
return self.fdb.getLastInsertId()
#stores a stud/razz hand into the database
def ring_stud(self, config, settings, db, cursor, base, category, site_hand_no, gametype_id, hand_start_time
,names, player_ids, start_cashes, antes, card_values, card_suits, winnings, rakes
,action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName
,seatNos):
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudImportData
,(None, None, None, None, None), (None, None, None, None, None))
#print "before calling store_hands_players_stud, antes:", antes
hands_players_ids = fpdb_simple.store_hands_players_stud(self.backend, db, cursor, hands_id, player_ids
,start_cashes, antes, card_values
,card_suits, winnings, rakes, seatNos)
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types
,allIns, action_amounts, actionNos)
return hands_id
#end def ring_stud
def ring_holdem_omaha(self, config, settings, db, cursor, base, category, site_hand_no, gametype_id
,hand_start_time, names, player_ids, start_cashes, positions, card_values
,card_suits, board_values, board_suits, winnings, rakes, action_types, allIns
,action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a holdem/omaha hand into the database"""
t0 = time()
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
t1 = time()
fpdb_simple.fill_board_cards(board_values, board_suits)
t2 = time()
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats,
hudImportData, board_values, board_suits)
#TEMPORARY CALL! - Just until all functions are migrated
t3 = time()
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha(
self.backend, db, cursor, category, hands_id, player_ids, start_cashes
, positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData)
t4 = time()
#print "ring holdem, backend=%d" % backend
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
t5 = time()
t6 = time()
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
t7 = time()
#print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4, t6-t5)
return hands_id
#end def ring_holdem_omaha
def tourney_holdem_omaha(self, config, settings, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout
,entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId
,siteId #end of tourney specific params
,site_hand_no, gametype_id, hand_start_time, names, player_ids
,start_cashes, positions, card_values, card_suits, board_values
,board_suits, winnings, rakes, action_types, allIns, action_amounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a tourney holdem/omaha hand into the database"""
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
fpdb_simple.fill_board_cards(board_values, board_suits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha_tourney(
self.backend, db, cursor, category, hands_id, player_ids, start_cashes, positions
, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids)
#print "tourney holdem, backend=%d" % backend
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
return hands_id
#end def tourney_holdem_omaha
def tourney_stud(self, config, settings, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries
,prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteId
,siteHandNo, gametypeId, handStartTime, names, playerIds, startCashes, antes
,cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
#stores a tourney stud/razz hand into the database
fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, playerIds, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(self.backend, db, cursor, siteHandNo, gametypeId, handStartTime, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_stud_tourney(self.backend, db, cursor, hands_id
, playerIds, startCashes, antes, cardValues, cardSuits
, winnings, rakes, seatNos, tourneys_players_ids)
if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop':
fpdb_simple.storeHudCache(self.backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
if self.saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)
return hands_id
#end def tourney_stud
def rebuild_hudcache(self):
"""clears hudcache and rebuilds from the individual handsplayers records"""
stime = time()
self.connection.cursor().execute(self.sql.query['clearHudCache'])
self.connection.cursor().execute(self.sql.query['rebuildHudCache'])
self.commit()
print "Rebuild hudcache took %.1f seconds" % (time() - stime,)
#end def rebuild_hudcache
def analyzeDB(self):
"""Do whatever the DB can offer to update index/table statistics"""
stime = time()
if self.backend == self.MYSQL_INNODB:
try:
self.cursor.execute(self.sql.query['analyze'])
except:
print "Error during analyze"
elif self.backend == self.PGSQL:
self.connection.set_isolation_level(0) # allow vacuum to work
try:
self.cursor = self.get_cursor()
self.cursor.execute(self.sql.query['analyze'])
except:
print "Error during analyze:", str(sys.exc_value)
self.connection.set_isolation_level(1) # go back to normal isolation level
self.commit()
atime = time() - stime
print "Analyze took %.1f seconds" % (atime,)
#end def analyzeDB
if __name__=="__main__": if __name__=="__main__":
c = Configuration.Config() c = Configuration.Config()

View File

@ -37,7 +37,7 @@ class Everleaf(HandHistoryConverter):
re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]") re_Board = re.compile(ur"\[ (?P<CARDS>.+) \]")
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False): def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, debugging=False, index=0):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
@ -45,7 +45,7 @@ follow : whether to tail -f the input
autostart: whether to run the thread (or you can call start() yourself) autostart: whether to run the thread (or you can call start() yourself)
debugging: if False, pass on partially supported game types. If true, have a go and error...""" debugging: if False, pass on partially supported game types. If true, have a go and error..."""
print "DEBUG: XXXXXXXXXXXXXXX" print "DEBUG: XXXXXXXXXXXXXXX"
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Everleaf", follow=follow) HandHistoryConverter.__init__(self, in_path, out_path, sitename="Everleaf", follow=follow, index=index)
logging.info("Initialising Everleaf converter class") logging.info("Initialising Everleaf converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
@ -237,11 +237,14 @@ or None if we fail to get the info """
# Also works with Omaha hands. # Also works with Omaha hands.
cards = m.group('CARDS') cards = m.group('CARDS')
cards = [card.strip() for card in cards.split(',')] cards = [card.strip() for card in cards.split(',')]
hand.addHoleCards(cards, m.group('PNAME')) # hand.addHoleCards(cards, m.group('PNAME'))
hand.addHoleCards('PREFLOP', hand.hero, closed=cards, shown=False, mucked=False, dealt=True)
else: else:
#Not involved in hand #Not involved in hand
hand.involved = False hand.involved = False
def readStudPlayerCards(self, hand, street): def readStudPlayerCards(self, hand, street):
# lol. see Plymouth.txt # lol. see Plymouth.txt
logging.warning("Everleaf readStudPlayerCards is only a stub.") logging.warning("Everleaf readStudPlayerCards is only a stub.")
@ -292,7 +295,8 @@ or None if we fail to get the info """
cards = cards.split(', ') cards = cards.split(', ')
player = m.group('PNAME') player = m.group('PNAME')
logging.debug("readShownCards %s cards=%s" % (player, cards)) logging.debug("readShownCards %s cards=%s" % (player, cards))
hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards) # hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards)
hand.addShownCards(cards=cards, player=m.group('PNAME'))

View File

@ -30,12 +30,11 @@ import fpdb_db
import FpdbSQLQueries import FpdbSQLQueries
class Filters(threading.Thread): class Filters(threading.Thread):
def __init__(self, db, settings, config, qdict, display = {},debug=True): def __init__(self, db, config, qdict, display = {}, debug=True):
self.debug=debug self.debug=debug
#print "start of GraphViewer constructor" #print "start of GraphViewer constructor"
self.db=db self.db=db
self.cursor=db.cursor self.cursor=db.cursor
self.settings=settings
self.sql=qdict self.sql=qdict
self.conf = config self.conf = config
self.display = display self.display = display
@ -235,7 +234,7 @@ class Filters(threading.Thread):
def __set_hero_name(self, w, site): def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text() self.heroes[site] = w.get_text()
# print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site]) # print "DEBUG: setting heroes[%s]: %s"%(site, self.heroes[site])
def createSiteLine(self, hbox, site): def createSiteLine(self, hbox, site):
cb = gtk.CheckButton(site) cb = gtk.CheckButton(site)
@ -556,23 +555,12 @@ def main(argv=None):
config = Configuration.Config() config = Configuration.Config()
db = None db = None
settings = {}
settings.update(config.get_db_parameters())
settings.update(config.get_tv_parameters())
settings.update(config.get_import_parameters())
settings.update(config.get_default_paths())
db = fpdb_db.fpdb_db() db = fpdb_db.fpdb_db()
db.connect(settings['db-backend'], db.do_connect(config)
settings['db-host'],
settings['db-databaseName'],
settings['db-user'],
settings['db-password'])
qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name()) qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name())
i = Filters(db, settings, config, qdict) i = Filters(db, config, qdict)
main_window = gtk.Window() main_window = gtk.Window()
main_window.connect('destroy', destroy) main_window.connect('destroy', destroy)
main_window.add(i.get_vbox()) main_window.add(i.get_vbox())

View File

@ -1,161 +0,0 @@
# pokerstars_cash.py
# -*- coding: iso-8859-15
#
# PokerStats, an online poker statistics tracking software for Linux
# Copyright (C) 2007-2008 Mika Boström <bostik@iki.fi>
#
# 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, version 3 of the License.
#
# Modified for use in fpdb by Carl Gherardi
import re
# These are PokerStars specific;
# More importantly, they are currently valid for cash game only.
#####
# XXX: There was a weird problem with saved hand histories in PokerStars
# client 2.491; if a user was present on the table (and thus anywhere in
# the hand history), with non-standard characters in their username, the
# client would prepend a literal Ctrl-P (ASCII 16, 0x10) character to
# the hand history title line. Hence, to allow these strangely saved
# hands to be parsed and imported, there is a conditional "one extra
# character" allowed at the start of the new hand regex.
class FpdbRegex:
def __init__(self):
self.__GAME_INFO_REGEX=''
self.__SPLIT_HAND_REGEX='\n\n\n'
self.__NEW_HAND_REGEX='^.?PokerStars Game #\d+:\s+Hold\'em'
self.__HAND_INFO_REGEX='^.*#(\d+):\s+(\S+)\s([\s\S]+)\s\(\$?([.0-9]+)/\$?([.0-9]+)\)\s-\s(\S+)\s-?\s?(\S+)\s\(?(\w+)\)?'
self.__TABLE_INFO_REGEX='^\S+\s+\'.*\'\s+(\d+)-max\s+Seat\s#(\d+)'
self.__PLAYER_INFO_REGEX='^Seat\s(\d+):\s(.*)\s\(\$?([.\d]+)\s'
self.__POST_SB_REGEX='^(.*):\sposts small blind'
self.__POST_BB_REGEX='^(.*):\sposts big blind'
self.__POST_BOTH_REGEX='^(.*):\sposts small & big blinds'
self.__HAND_STAGE_REGEX='^\*{3}\s(.*)\s\*{3}'
self.__HOLE_CARD_REGEX='^\*{3}\sHOLE CARDS'
self.__FLOP_CARD_REGEX='^\*{3}\sFLOP\s\*{3}\s\[(\S{2})\s(\S{2})\s(\S{2})\]'
self.__TURN_CARD_REGEX='^\*{3}\sTURN\s\*{3}\s\[\S{2}\s\S{2}\s\S{2}\]\s\[(\S{2})\]'
self.__RIVER_CARD_REGEX='^\*{3}\sRIVER\s\*{3}\s\[\S{2}\s\S{2}\s\S{2}\s\S{2}\]\s\[(\S{2})\]'
self.__SHOWDOWN_REGEX='^\*{3}\sSHOW DOWN'
self.__SUMMARY_REGEX='^\*{3}\sSUMMARY'
self.__UNCALLED_BET_REGEX='^Uncalled bet \(\$([.\d]+)\) returned to (.*)'
self.__POT_AND_RAKE_REGEX='^Total\spot\s\$([.\d]+).*\|\sRake\s\$([.\d]+)'
self.__COLLECT_POT_REGEX='^(.*)\scollected\s\$([.\d]+)\sfrom\s((main|side)\s)?pot'
self.__HERO_CARDS_REGEX='^Dealt\sto\s(.*)\s\[(\S{2})\s(\S{2})\]'
self.__SHOWN_CARDS_REGEX='^(.*):\sshows\s\[(\S{2})\s(\S{2})\]'
self.__ACTION_STEP_REGEX='^(.*):\s(bets|checks|raises|calls|folds)((\s\$([.\d]+))?(\sto\s\$([.\d]+))?)?'
self.__SHOWDOWN_ACTION_REGEX='^(.*):\s(shows|mucks)'
self.__SUMMARY_CARDS_REGEX='^Seat\s\d+:\s(.*)\s(showed|mucked)\s\[(\S{2})\s(\S{2})\]'
self.__SUMMARY_CARDS_EXTRA_REGEX='^Seat\s\d+:\s(.*)\s(\(.*\)\s)(showed|mucked)\s\[(\S{2})\s(\S{2})\]'
def compileRegexes(self):
### Compile the regexes
self.game_info_re = re.compile(self.__GAME_INFO_REGEX)
self.split_hand_re = re.compile(self.__SPLIT_HAND_REGEX)
self.hand_start_re = re.compile(self.__NEW_HAND_REGEX)
self.hand_info_re = re.compile(self.__HAND_INFO_REGEX)
self.table_info_re = re.compile(self.__TABLE_INFO_REGEX)
self.player_info_re = re.compile(self.__PLAYER_INFO_REGEX)
self.small_blind_re = re.compile(self.__POST_SB_REGEX)
self.big_blind_re = re.compile(self.__POST_BB_REGEX)
self.both_blinds_re = re.compile(self.__POST_BOTH_REGEX)
self.hand_stage_re = re.compile(self.__HAND_STAGE_REGEX)
self.hole_cards_re = re.compile(self.__HOLE_CARD_REGEX)
self.flop_cards_re = re.compile(self.__FLOP_CARD_REGEX)
self.turn_card_re = re.compile(self.__TURN_CARD_REGEX)
self.river_card_re = re.compile(self.__RIVER_CARD_REGEX)
self.showdown_re = re.compile(self.__SHOWDOWN_REGEX)
self.summary_re = re.compile(self.__SUMMARY_REGEX)
self.uncalled_bet_re = re.compile(self.__UNCALLED_BET_REGEX)
self.collect_pot_re = re.compile(self.__COLLECT_POT_REGEX)
self.hero_cards_re = re.compile(self.__HERO_CARDS_REGEX)
self.cards_shown_re = re.compile(self.__SHOWN_CARDS_REGEX)
self.summary_cards_re = re.compile(self.__SUMMARY_CARDS_REGEX)
self.summary_cards_extra_re = re.compile(self.__SUMMARY_CARDS_EXTRA_REGEX)
self.action_re = re.compile(self.__ACTION_STEP_REGEX)
self.rake_re = re.compile(self.__POT_AND_RAKE_REGEX)
self.showdown_action_re = re.compile(self.__SHOWDOWN_ACTION_REGEX)
# Set methods for plugins to override
def setGameInfoRegex(self, string):
self.__GAME_INFO_REGEX = string
def setSplitHandRegex(self, string):
self.__SPLIT_HAND_REGEX = string
def setNewHandRegex(self, string):
self.__NEW_HAND_REGEX = string
def setHandInfoRegex(self, string):
self.__HAND_INFO_REGEX = string
def setTableInfoRegex(self, string):
self.__TABLE_INFO_REGEX = string
def setPlayerInfoRegex(self, string):
self.__PLAYER_INFO_REGEX = string
def setPostSbRegex(self, string):
self.__POST_SB_REGEX = string
def setPostBbRegex(self, string):
self.__POST_BB_REGEX = string
def setPostBothRegex(self, string):
self.__POST_BOTH_REGEX = string
def setHandStageRegex(self, string):
self.__HAND_STAGE_REGEX = string
def setHoleCardRegex(self, string):
self.__HOLE_CARD_REGEX = string
def setFlopCardRegex(self, string):
self.__FLOP_CARD_REGEX = string
def setTurnCardRegex(self, string):
self.__TURN_CARD_REGEX = string
def setRiverCardRegex(self, string):
self.__RIVER_CARD_REGEX = string
def setShowdownRegex(self, string):
self.__SHOWDOWN_REGEX = string
def setSummaryRegex(self, string):
self.__SUMMARY_REGEX = string
def setUncalledBetRegex(self, string):
self.__UNCALLED_BET_REGEX = string
def setCollectPotRegex(self, string):
self.__COLLECT_POT_REGEX = string
def setHeroCardsRegex(self, string):
self.__HERO_CARDS_REGEX = string
def setShownCardsRegex(self, string):
self.__SHOWN_CARDS_REGEX = string
def setSummaryCardsRegex(self, string):
self.__SUMMARY_CARDS_REGEX = string
def setSummaryCardsExtraRegex(self, string):
self.__SUMMARY_CARDS_EXTRA_REGEX = string
def setActionStepRegex(self, string):
self.__ACTION_STEP_REGEX = string
def setPotAndRakeRegex(self, string):
self.__POT_AND_RAKE_REGEX = string
def setShowdownActionRegex(self, string):
self.__SHOWDOWN_ACTION_REGEX = string

File diff suppressed because it is too large Load Diff

View File

@ -23,25 +23,49 @@ import logging
from HandHistoryConverter import * from HandHistoryConverter import *
# Fulltilt HH Format converter # Fulltilt HH Format converter
# TODO: cat tourno and table to make table name for tournaments
class Fulltilt(HandHistoryConverter): class Fulltilt(HandHistoryConverter):
# Static regexes # Static regexes
re_GameInfo = re.compile('- (?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (Ante \$(?P<ANTE>[.0-9]+) )?- (?P<LIMIT>(No Limit|Pot Limit|Limit))? (?P<GAME>(Hold\'em|Omaha Hi|Razz))') re_GameInfo = re.compile('''(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
.+
-\s(?P<CURRENCY>\$|)?
(?P<SB>[.0-9]+)/
\$?(?P<BB>[.0-9]+)\s
(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
(?P<LIMIT>(No\sLimit|Pot\sLimit|Limit))?\s
(?P<GAME>(Hold\'em|Omaha\sHi|Omaha\sH/L|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))
''', re.VERBOSE)
re_SplitHands = re.compile(r"\n\n+") re_SplitHands = re.compile(r"\n\n+")
re_TailSplitHands = re.compile(r"(\n\n+)") re_TailSplitHands = re.compile(r"(\n\n+)")
re_HandInfo = re.compile('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[- a-zA-Z]+) (\((?P<TABLEATTRIBUTES>.+)\) )?- \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (Ante \$(?P<ANTE>[.0-9]+) )?- (?P<GAMETYPE>[a-zA-Z\' ]+) - (?P<DATETIME>.*)') re_HandInfo = re.compile('''.*\#(?P<HID>[0-9]+):\s
(?:(?P<TOURNAMENT>.+)\s\((?P<TOURNO>\d+)\),\s)?
Table\s
(?P<PLAY>Play\sChip\s|PC)?
(?P<TABLE>[-\s\da-zA-Z]+)\s
(\((?P<TABLEATTRIBUTES>.+)\)\s)?-\s
\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\s(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
(?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s
(?P<DATETIME>.*)
''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[.0-9]+)\)\n') re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# These regexes are for FTP only
re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE)
re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE)
# NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports. # NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports.
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True): mixes = { 'HORSE': 'horse', '7-Game': '7game', 'HOSE': 'hose', 'HA': 'ha'}
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
follow : whether to tail -f the input""" follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Fulltilt", follow=follow) HandHistoryConverter.__init__(self, in_path, out_path, sitename="Fulltilt", follow=follow, index=index)
logging.info("Initialising Fulltilt converter class") logging.info("Initialising Fulltilt converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
@ -63,19 +87,24 @@ follow : whether to tail -f the input"""
self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBoth = re.compile(r"^%s posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE) self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s(?P<ATYPE> bets| checks| raises to| completes it to| calls| folds)(\s\$(?P<BET>[.\d]+))?" % player_re, re.MULTILINE) self.re_Action = re.compile(r"^%s(?P<ATYPE> bets| checks| raises to| completes it to| calls| folds)( \$?(?P<BET>[.,\d]+))?" % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(r"^%s shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*)" % player_re, re.MULTILINE) self.re_CollectPot = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*)" % player_re, re.MULTILINE)
self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE) self.re_SitsOut = re.compile(r"^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE) self.re_ShownCards = re.compile(r"^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self): def readSupportedGames(self):
return [["ring", "hold", "nl"], return [["ring", "hold", "nl"],
["ring", "hold", "pl"], ["ring", "hold", "pl"],
["ring", "hold", "fl"], ["ring", "hold", "fl"],
["ring", "stud", "fl"], ["ring", "stud", "fl"],
["ring", "omaha", "pl"]
["tour", "hold", "nl"],
["tour", "hold", "pl"],
["tour", "hold", "fl"],
["tour", "stud", "fl"],
] ]
def determineGameType(self, handText): def determineGameType(self, handText):
@ -88,7 +117,6 @@ follow : whether to tail -f the input"""
m = self.re_GameInfo.search(handText) m = self.re_GameInfo.search(handText)
if not m: if not m:
return None return None
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to our info strings # translations from captured groups to our info strings
@ -96,39 +124,41 @@ follow : whether to tail -f the input"""
games = { # base, category games = { # base, category
"Hold'em" : ('hold','holdem'), "Hold'em" : ('hold','holdem'),
'Omaha Hi' : ('hold','omahahi'), 'Omaha Hi' : ('hold','omahahi'),
'Omaha H/L' : ('hold','omahahilo'),
'Razz' : ('stud','razz'), 'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi') 'Stud Hi' : ('stud','studhi'),
'Stud H/L' : ('stud','studhilo')
} }
currencies = { u'':'EUR', '$':'USD', '':'T$' } currencies = { u'':'EUR', '$':'USD', '':'T$' }
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']] info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg:
(info['base'], info['category']) = games[mg['GAME']]
if 'SB' in mg:
info['sb'] = mg['SB'] info['sb'] = mg['SB']
if 'BB' in mg:
info['bb'] = mg['BB'] info['bb'] = mg['BB']
if 'CURRENCY' in mg: if mg['GAME'] != None:
(info['base'], info['category']) = games[mg['GAME']]
if mg['CURRENCY'] != None:
info['currency'] = currencies[mg['CURRENCY']] info['currency'] = currencies[mg['CURRENCY']]
if mg['TOURNO'] == None: info['type'] = "ring"
else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info return info
def readHandInfo(self, hand): def readHandInfo(self, hand):
m = self.re_HandInfo.search(hand.handText,re.DOTALL) m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if(m == None): if(m == None):
logging.info("Didn't match re_HandInfo") logging.info("Didn't match re_HandInfo")
logging.info(hand.handText) logging.info(hand.handText)
return None return None
hand.handid = m.group('HID') hand.handid = m.group('HID')
hand.tablename = m.group('TABLE') hand.tablename = m.group('TABLE')
hand.starttime = datetime.datetime.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'): if m.group('TABLEATTRIBUTES'):
m2 = re.search("(deep )?(\d+)( max)?", m.group('TABLEATTRIBUTES')) m2 = self.re_Max.search(m.group('TABLEATTRIBUTES'))
hand.maxseats = int(m2.group(2)) if m2: hand.maxseats = int(m2.group('MAX'))
hand.tourNo = m.group('TOURNO')
if m.group('PLAY') != None:
hand.gametype['currency'] = 'play'
# These work, but the info is already in the Hand class - should be used for tourneys though. # These work, but the info is already in the Hand class - should be used for tourneys though.
# m.group('SB') # m.group('SB')
# m.group('BB') # m.group('BB')
@ -190,6 +220,7 @@ follow : whether to tail -f the input"""
m = self.re_Antes.finditer(hand.handText) m = self.re_Antes.finditer(hand.handText)
for player in m: for player in m:
logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE'))) logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
# if player.group() !=
hand.addAnte(player.group('PNAME'), player.group('ANTE')) hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand): def readBringIn(self, hand):
@ -198,79 +229,46 @@ follow : whether to tail -f the input"""
logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN'))) logging.debug("Player bringing in: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
else: else:
logging.warning("No bringin found") logging.warning("No bringin found, handid =%s" % hand.handid)
def readButton(self, hand): def readButton(self, hand):
hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON')) hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
def readHeroCards(self, hand): def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.handText) # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
if(m == None): # we need to grab hero's cards
#Not involved in hand for street in ('PREFLOP', 'DEAL'):
hand.involved = False if street in hand.streets.keys():
else:
hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('NEWCARDS')
cards = [c.strip() for c in cards.split(' ')]
hand.addHoleCards(cards, m.group('PNAME'))
def readStudPlayerCards(self, hand, street):
# This could be the most tricky one to get right.
# It looks for cards dealt in 'street',
# which may or may not be in the section of the hand designated 'street' by markStreets earlier.
# Here's an example at FTP of what 'THIRD' and 'FOURTH' look like to hero PokerAscetic
#
#"*** 3RD STREET ***
#Dealt to BFK23 [Th]
#Dealt to cutiepr1nnymaid [8c]
#Dealt to PokerAscetic [7c 8s] [3h]
#..."
#
#"*** 4TH STREET ***
#Dealt to cutiepr1nnymaid [8c] [2s]
#Dealt to PokerAscetic [7c 8s 3h] [5s]
#..."
#Note that hero's first two holecards are only reported at 3rd street as 'old' cards.
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street]) m = self.re_HeroCards.finditer(hand.streets[street])
for player in m: for found in m:
logging.debug(player.groupdict()) # if m == None:
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS')) # hand.involved = False
if oldcards: # else:
oldcards = [c.strip() for c in oldcards.split(' ')] hand.hero = found.group('PNAME')
if newcards: newcards = found.group('NEWCARDS').split(' ')
newcards = [c.strip() for c in newcards.split(' ')] hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
# options here:
# (1) we trust the hand will know what to do -- probably check that the old cards match what it already knows, and add the newcards to this street. for street, text in hand.streets.iteritems():
# (2) we're the experts at this particular history format and we know how we're going to be called (once for each street in Hand.streetList) if not text or street in ('PREFLOP', 'DEAL'): continue # already done these
# so call addPlayerCards with the appropriate information. m = self.re_HeroCards.finditer(hand.streets[street])
# I favour (2) here but I'm afraid it is rather stud7-specific. for found in m:
# in the following, the final list of cards will be in 'newcards' whilst if the first list exists (most of the time it does) it will be in 'oldcards' player = found.group('PNAME')
if street=='ANTES': if found.group('NEWCARDS') == None:
return newcards = []
elif street=='THIRD': else:
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS' newcards = found.group('NEWCARDS').split(' ')
# hero: [xx][o] if found.group('OLDCARDS') == None:
# others: [o] oldcards = []
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards) else:
elif street in ('FOURTH', 'FIFTH', 'SIXTH'): oldcards = found.group('OLDCARDS').split(' ')
# 4th:
# hero: [xxo] [o] if street == 'THIRD' and len(oldcards) == 2: # hero in stud game
# others: [o] [o] hand.hero = player
# 5th: hand.dealt.add(player) # need this for stud??
# hero: [xxoo] [o] hand.addHoleCards(street, player, closed=oldcards, open=newcards, shown=False, mucked=False, dealt=False)
# others: [oo] [o] else:
# 6th: hand.addHoleCards(street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False)
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
@ -308,6 +306,29 @@ follow : whether to tail -f the input"""
cards = cards.split(' ') cards = cards.split(' ')
hand.addShownCards(cards=cards, player=m.group('PNAME')) hand.addShownCards(cards=cards, player=m.group('PNAME'))
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy
if hand.gametype['base'] == 'stud':
if mo <= 8: return 8
else: return mo
if hand.gametype['base'] == 'draw':
if mo <= 6: return 6
else: return mo
if mo == 2: return 2
if mo <= 6: return 6
return 9
def readOther(self, hand):
m = self.re_Mixed.search(self.in_path)
if m == None: hand.mixed = None
else:
hand.mixed = self.mixes[m.groupdict()['MIXED']]
if __name__ == "__main__": if __name__ == "__main__":
parser = OptionParser() parser = OptionParser()

View File

@ -28,6 +28,7 @@ import time
import fpdb_import import fpdb_import
from optparse import OptionParser from optparse import OptionParser
import Configuration import Configuration
import string
class GuiAutoImport (threading.Thread): class GuiAutoImport (threading.Thread):
def __init__(self, settings, config): def __init__(self, settings, config):
@ -153,25 +154,25 @@ class GuiAutoImport (threading.Thread):
# results to the same pipe. This means that self.path should be a a list of dirs # results to the same pipe. This means that self.path should be a a list of dirs
# to watch. # to watch.
if widget.get_active(): # toggled on if widget.get_active(): # toggled on
# - Does the lock acquisition need to be more sophisticated for multiple dirs?
# (see comment above about what to do if pipe already open)
# - Ideally we want to release the lock if the auto-import is killed by some
# kind of exception - is this possible?
if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired
print "\nGlobal lock taken ..."
self.doAutoImportBool = True self.doAutoImportBool = True
widget.set_label(u' _Stop Autoimport ') widget.set_label(u' _Stop Autoimport ')
if self.pipe_to_hud is None: if self.pipe_to_hud is None:
if os.name == 'nt': if os.name == 'nt':
command = "python HUD_main.py" + " %s" % (self.database) command = "python HUD_main.py" + " " + self.settings['cl_options']
bs = 0 # windows is not happy with line buffing here bs = 0 # windows is not happy with line buffing here
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
else: else:
command = os.path.join(sys.path[0], 'HUD_main.py') command = os.path.join(sys.path[0], 'HUD_main.py')
#command = self.config.execution_path('HUD_main.py') # Hi Ray. Sorry about this, kludging. cl = [command, ] + string.split(self.settings['cl_options'])
bs = 1 self.pipe_to_hud = subprocess.Popen(cl, bufsize = 1, stdin = subprocess.PIPE,
self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
# universal_newlines=True)
# command = command + " %s" % (self.database)
# print "command = ", command
# self.pipe_to_hud = os.popen(command, 'w')
# Add directories to importer object. # Add directories to importer object.
for site in self.input_settings: for site in self.input_settings:
@ -181,9 +182,12 @@ class GuiAutoImport (threading.Thread):
interval=int(self.intervalEntry.get_text()) interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import) gobject.timeout_add(interval*1000, self.do_import)
else:
print "auto-import aborted - global lock not available"
else: # toggled off else: # toggled off
self.settings['global_lock'].release()
self.doAutoImportBool = False # do_import will return this and stop the gobject callback timer self.doAutoImportBool = False # do_import will return this and stop the gobject callback timer
print "Stopping autoimport" print "Stopping autoimport - global lock released."
if self.pipe_to_hud.poll() is not None: if self.pipe_to_hud.poll() is not None:
print "HUD already terminated" print "HUD already terminated"
else: else:
@ -192,8 +196,6 @@ class GuiAutoImport (threading.Thread):
self.pipe_to_hud = None self.pipe_to_hud = None
self.startButton.set_label(u' _Start Autoimport ') self.startButton.set_label(u' _Start Autoimport ')
#end def GuiAutoImport.startClicked #end def GuiAutoImport.startClicked
def get_vbox(self): def get_vbox(self):

View File

@ -49,6 +49,11 @@ class GuiBulkImport():
self.importer.RunImportThreaded() self.importer.RunImportThreaded()
def load_clicked(self, widget, data=None): def load_clicked(self, widget, data=None):
# Does the lock acquisition need to be more sophisticated for multiple dirs?
# (see comment above about what to do if pipe already open)
if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired
try:
print "\nGlobal lock taken ..."
# get the dir to import from the chooser # get the dir to import from the chooser
self.inputFile = self.chooser.get_filename() self.inputFile = self.chooser.get_filename()
@ -80,6 +85,11 @@ class GuiBulkImport():
self.importer.clearFileList() self.importer.clearFileList()
self.lab_info.set_text("Import finished") self.lab_info.set_text("Import finished")
except:
pass
self.settings['global_lock'].release()
else:
print "bulk-import aborted - global lock not available"
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
@ -88,8 +98,7 @@ class GuiBulkImport():
def __init__(self, settings, config): def __init__(self, settings, config):
self.settings = settings self.settings = settings
self.config = config self.config = config
self.importer = fpdb_import.Importer(self, self.settings, self.importer = fpdb_import.Importer(self, self.settings, config)
config)
self.vbox = gtk.VBox(False, 0) self.vbox = gtk.VBox(False, 0)
self.vbox.show() self.vbox.show()
@ -196,10 +205,11 @@ class GuiBulkImport():
self.load_button.show() self.load_button.show()
# see how many hands are in the db and adjust accordingly # see how many hands are in the db and adjust accordingly
tcursor = self.importer.fdb.db.cursor() tcursor = self.importer.database.cursor
tcursor.execute("Select count(1) from Hands") tcursor.execute("Select count(1) from Hands")
row = tcursor.fetchone() row = tcursor.fetchone()
tcursor.close() tcursor.close()
self.importer.database.rollback()
self.n_hands_in_db = row[0] self.n_hands_in_db = row[0]
if self.n_hands_in_db == 0: if self.n_hands_in_db == 0:
self.cb_dropindexes.set_active(2) self.cb_dropindexes.set_active(2)

View File

@ -38,20 +38,19 @@ except:
and HUD are NOT affected by this problem.""" and HUD are NOT affected by this problem."""
import fpdb_import import fpdb_import
import fpdb_db import Database
import Filters import Filters
class GuiGraphViewer (threading.Thread): class GuiGraphViewer (threading.Thread):
def __init__(self, db, settings, querylist, config, debug=True): def __init__(self, querylist, config, debug=True):
"""Constructor for GraphViewer""" """Constructor for GraphViewer"""
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql = querylist self.sql = querylist
self.conf = config self.conf = config
self.debug = debug
#print "start of GraphViewer constructor"
self.db = Database.Database(self.conf, sql=self.sql)
filters_display = { "Heroes" : True, filters_display = { "Heroes" : True,
"Sites" : True, "Sites" : True,
@ -63,7 +62,7 @@ class GuiGraphViewer (threading.Thread):
"Button2" : True "Button2" : True
} }
self.filters = Filters.Filters(db, settings, config, querylist, display = filters_display) self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton1Name("Refresh Graph") self.filters.registerButton1Name("Refresh Graph")
self.filters.registerButton1Callback(self.generateGraph) self.filters.registerButton1Callback(self.generateGraph)
self.filters.registerButton2Name("Export to File") self.filters.registerButton2Name("Export to File")
@ -90,7 +89,7 @@ class GuiGraphViewer (threading.Thread):
self.canvas = None self.canvas = None
self.db.db.rollback() self.db.rollback()
################################# #################################
# #
@ -126,7 +125,7 @@ class GuiGraphViewer (threading.Thread):
#end def get_vbox #end def get_vbox
def clearGraphData(self): def clearGraphData(self):
self.fig.clf() self.fig.clear()
if self.canvas is not None: if self.canvas is not None:
self.canvas.destroy() self.canvas.destroy()
@ -146,7 +145,7 @@ class GuiGraphViewer (threading.Thread):
for site in sites: for site in sites:
if sites[site] == True: if sites[site] == True:
sitenos.append(siteids[site]) sitenos.append(siteids[site])
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],)) self.db.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
result = self.db.cursor.fetchall() result = self.db.cursor.fetchall()
if len(result) == 1: if len(result) == 1:
playerids.append(result[0][0]) playerids.append(result[0][0])
@ -154,7 +153,7 @@ class GuiGraphViewer (threading.Thread):
if not sitenos: if not sitenos:
#Should probably pop up here. #Should probably pop up here.
print "No sites selected - defaulting to PokerStars" print "No sites selected - defaulting to PokerStars"
sitenos = [2] return
if not playerids: if not playerids:
print "No player ids found" print "No player ids found"
@ -197,6 +196,7 @@ class GuiGraphViewer (threading.Thread):
self.graphBox.add(self.canvas) self.graphBox.add(self.canvas)
self.canvas.show() self.canvas.show()
self.canvas.draw()
#self.exportButton.set_sensitive(True) #self.exportButton.set_sensitive(True)
#end of def showClicked #end of def showClicked
@ -205,7 +205,7 @@ class GuiGraphViewer (threading.Thread):
# print "DEBUG: getRingProfitGraph" # print "DEBUG: getRingProfitGraph"
start_date, end_date = self.filters.getDates() start_date, end_date = self.filters.getDates()
#Buggered if I can find a way to do this 'nicely' take a list of intergers and longs #Buggered if I can find a way to do this 'nicely' take a list of integers and longs
# and turn it into a tuple readale by sql. # and turn it into a tuple readale by sql.
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829) # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
nametest = str(tuple(names)) nametest = str(tuple(names))
@ -226,10 +226,10 @@ class GuiGraphViewer (threading.Thread):
#print "DEBUG: sql query:" #print "DEBUG: sql query:"
#print tmp #print tmp
self.cursor.execute(tmp) self.db.cursor.execute(tmp)
#returns (HandId,Winnings,Costs,Profit) #returns (HandId,Winnings,Costs,Profit)
winnings = self.db.cursor.fetchall() winnings = self.db.cursor.fetchall()
self.db.db.rollback() self.db.rollback()
if(winnings == ()): if(winnings == ()):
return None return None

View File

@ -20,34 +20,35 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os import os
import sys
from time import time, strftime from time import time, strftime
import Card import Card
import fpdb_import import fpdb_import
import Database
import fpdb_db import fpdb_db
import Filters import Filters
import FpdbSQLQueries
class GuiPlayerStats (threading.Thread): class GuiPlayerStats (threading.Thread):
def __init__(self, config, querylist, mainwin, debug=True): def __init__(self, config, querylist, mainwin, debug=True):
self.debug = debug self.debug = debug
self.conf = config self.conf = config
self.main_window = mainwin self.main_window = mainwin
self.sql = querylist
self.MYSQL_INNODB = 2 self.MYSQL_INNODB = 2
self.PGSQL = 3 self.PGSQL = 3
self.SQLITE = 4 self.SQLITE = 4
# create new db connection to avoid conflicts with other threads # create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db() self.db = Database.Database(self.conf, sql=self.sql)
self.db.do_connect(self.conf)
self.cursor = self.db.cursor self.cursor = self.db.cursor
self.sql = querylist
settings = {} settings = {}
settings.update(config.get_db_parameters()) settings.update(self.conf.get_db_parameters())
settings.update(config.get_tv_parameters()) settings.update(self.conf.get_tv_parameters())
settings.update(config.get_import_parameters()) settings.update(self.conf.get_import_parameters())
settings.update(config.get_default_paths()) settings.update(self.conf.get_default_paths())
# text used on screen stored here so that it can be configured # text used on screen stored here so that it can be configured
self.filterText = {'handhead':'Hand Breakdown for all levels listed above' self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
@ -66,7 +67,7 @@ class GuiPlayerStats (threading.Thread):
"Button2" : True "Button2" : True
} }
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display) self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton1Name("_Filters") self.filters.registerButton1Name("_Filters")
self.filters.registerButton1Callback(self.showDetailFilter) self.filters.registerButton1Callback(self.showDetailFilter)
self.filters.registerButton2Name("_Refresh") self.filters.registerButton2Name("_Refresh")
@ -216,7 +217,7 @@ class GuiPlayerStats (threading.Thread):
flags = [True] flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates) self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats, groups, dates)
self.db.db.commit() self.db.rollback()
print "Stats page displayed in %4.2f seconds" % (time() - starttime) print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox): #end def fillStatsFrame(self, vbox):
@ -227,11 +228,6 @@ class GuiPlayerStats (threading.Thread):
if not flags: holecards = False if not flags: holecards = False
else: holecards = flags[0] else: holecards = flags[0]
self.stats_table = gtk.Table(1, 1, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
tmp = self.sql.query[query] tmp = self.sql.query[query]
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, groups, dates) tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, seats, groups, dates)
self.cursor.execute(tmp) self.cursor.execute(tmp)
@ -279,18 +275,16 @@ class GuiPlayerStats (threading.Thread):
while sqlrow < rows: while sqlrow < rows:
treerow = [] treerow = []
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col,column in enumerate(cols_to_show): for col,column in enumerate(cols_to_show):
if column[colalias] in colnames: if column[colalias] in colnames:
value = result[sqlrow][colnames.index(column[colalias])] value = result[sqlrow][colnames.index(column[colalias])]
if column[colalias] == 'plposition': if column[colalias] == 'plposition':
if value == 'B': if value == 'B':
value = 'BB' value = 'BB'
if value == 'S': elif value == 'S':
value = 'SB' value = 'SB'
elif value == '0':
value = 'Btn'
else: else:
if column[colalias] == 'game': if column[colalias] == 'game':
if holecards: if holecards:
@ -388,7 +382,8 @@ class GuiPlayerStats (threading.Thread):
# Group by position? # Group by position?
if groups['posn']: if groups['posn']:
query = query.replace("<position>", 'hp.position') #query = query.replace("<position>", "case hp.position when '0' then 'Btn' else hp.position end")
query = query.replace("<position>", "hp.position")
# set flag in self.columns to show posn column # set flag in self.columns to show posn column
[x for x in self.columns if x[0] == 'plposition'][0][1] = True [x for x in self.columns if x[0] == 'plposition'][0][1] = True
else: else:

View File

@ -20,9 +20,10 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os import os
from time import time, strftime
import fpdb_import import fpdb_import
import fpdb_db import Database
import Filters import Filters
import FpdbSQLQueries import FpdbSQLQueries
@ -30,21 +31,20 @@ class GuiPositionalStats (threading.Thread):
def __init__(self, config, querylist, debug=True): def __init__(self, config, querylist, debug=True):
self.debug = debug self.debug = debug
self.conf = config self.conf = config
self.sql = querylist
self.MYSQL_INNODB = 2 self.MYSQL_INNODB = 2
self.PGSQL = 3 self.PGSQL = 3
self.SQLITE = 4 self.SQLITE = 4
# create new db connection to avoid conflicts with other threads # create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db() self.db = Database.Database(self.conf, sql=self.sql)
self.db.do_connect(self.conf)
self.cursor = self.db.cursor self.cursor = self.db.cursor
self.sql = querylist
settings = {} settings = {}
settings.update(config.get_db_parameters()) settings.update(self.conf.get_db_parameters())
settings.update(config.get_tv_parameters()) settings.update(self.conf.get_tv_parameters())
settings.update(config.get_import_parameters()) settings.update(self.conf.get_import_parameters())
settings.update(config.get_default_paths()) settings.update(self.conf.get_default_paths())
filters_display = { "Heroes" : True, filters_display = { "Heroes" : True,
"Sites" : True, "Sites" : True,
@ -58,21 +58,50 @@ class GuiPositionalStats (threading.Thread):
"Button2" : False "Button2" : False
} }
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display) self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton1Name("Refresh") self.filters.registerButton1Name("Refresh")
self.filters.registerButton1Callback(self.refreshStats) self.filters.registerButton1Callback(self.refreshStats)
# ToDo: store in config
# 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
, ["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"]
]
self.stat_table = None self.stat_table = None
self.stats_frame = None self.stats_frame = None
self.stats_vbox = None
self.main_hbox = gtk.HBox(False, 0) self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show() self.main_hbox.show()
statsFrame = gtk.Frame("Stats:") self.stats_frame = gtk.Frame()
statsFrame.set_label_align(0.0, 0.0) self.stats_frame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame.show() self.stats_frame.show()
self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show()
# This could be stored in config eventually, or maybe configured in this window somehow. # This could be stored in config eventually, or maybe configured in this window somehow.
# Each posncols element is the name of a column returned by the sql # Each posncols element is the name of a column returned by the sql
@ -90,11 +119,11 @@ class GuiPositionalStats (threading.Thread):
, "PoFAFq", "Net($)", "bb/100", "$/hand", "Variance", "Hds" , "PoFAFq", "Net($)", "bb/100", "$/hand", "Variance", "Hds"
) )
self.fillStatsFrame(self.stats_frame) self.fillStatsFrame(self.stats_vbox)
statsFrame.add(self.stats_frame) self.stats_frame.add(self.stats_vbox)
self.main_hbox.pack_start(self.filters.get_vbox()) self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(statsFrame) self.main_hbox.pack_start(self.stats_frame)
def get_vbox(self): def get_vbox(self):
@ -107,9 +136,12 @@ class GuiPositionalStats (threading.Thread):
print "DEBUG: activesite set to %s" %(self.activesite) print "DEBUG: activesite set to %s" %(self.activesite)
def refreshStats(self, widget, data): def refreshStats(self, widget, data):
try: self.stats_table.destroy() try: self.stats_vbox.destroy()
except AttributeError: pass except AttributeError: pass
self.fillStatsFrame(self.stats_frame) self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox)
self.fillStatsFrame(self.stats_vbox)
def fillStatsFrame(self, vbox): def fillStatsFrame(self, vbox):
sites = self.filters.getSites() sites = self.filters.getSites()
@ -144,66 +176,104 @@ class GuiPositionalStats (threading.Thread):
self.createStatsTable(vbox, playerids, sitenos, limits, seats, dates) self.createStatsTable(vbox, playerids, sitenos, limits, seats, dates)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates): def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates):
self.stats_table = gtk.Table(1, 1, False) # gtk table expands as required
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
starttime = time()
colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4
row = 0 row = 0
col = 0 col = 0
for t in self.posnheads:
l = gtk.Label(self.posnheads[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1, yoptions=gtk.SHRINK)
col +=1
tmp = self.sql.query['playerStatsByPosition'] tmp = self.sql.query['playerStatsByPosition']
tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates) tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates)
self.cursor.execute(tmp) self.cursor.execute(tmp)
result = self.cursor.fetchall() result = self.cursor.fetchall()
colnames = [desc[0].lower() for desc in self.cursor.description]
liststore = gtk.ListStore(*([str] * len(colnames)))
view = gtk.TreeView(model=liststore)
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
vbox.pack_start(view, expand=False, padding=3)
# left-aligned cells:
textcell = gtk.CellRendererText()
# centred cells:
textcell50 = gtk.CellRendererText()
textcell50.set_property('xalign', 0.5)
# right-aligned cells:
numcell = gtk.CellRendererText()
numcell.set_property('xalign', 1.0)
listcols = []
for t in self.posnheads:
listcols.append(gtk.TreeViewColumn(self.posnheads[col]))
view.append_column(listcols[col])
if col == 0:
listcols[col].pack_start(textcell, expand=True)
listcols[col].add_attribute(textcell, 'text', col)
listcols[col].set_expand(True)
elif col in (1, 2):
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_expand(True)
col +=1
# Code below to be used when full column data structures implemented like in player stats:
# Create header row eg column: ("game", True, "Game", 0.0, "%s")
#for col, column in enumerate(cols_to_show):
# if column[colalias] == 'game' and holecards:
# s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
# else:
# s = column[colheading]
# listcols.append(gtk.TreeViewColumn(s))
# view.append_column(listcols[col])
# if column[colformat] == '%s':
# if column[colxalign] == 0.0:
# listcols[col].pack_start(textcell, expand=True)
# listcols[col].add_attribute(textcell, 'text', col)
# else:
# 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_expand(True)
# #listcols[col].set_alignment(column[colxalign]) # no effect?
rows = len(result) rows = len(result)
colnames = [desc[0].lower() for desc in self.cursor.description]
last_game,last_seats,sqlrow = "","",0 last_game,last_seats,sqlrow = "","",0
while sqlrow < rows: while sqlrow < rows:
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
rowprinted=0 rowprinted=0
treerow = []
avgcol = colnames.index('avgseats') avgcol = colnames.index('avgseats')
for col,colname in enumerate(self.posncols): for col,colname in enumerate(self.posncols):
if colname in colnames: if colname in colnames:
sqlcol = colnames.index(colname) sqlcol = colnames.index(colname)
else: else:
continue continue
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
# print blank row between levels:
if result[sqlrow][sqlcol]: if result[sqlrow][sqlcol]:
if sqlrow == 0: if sqlrow == 0:
l = gtk.Label(result[sqlrow][sqlcol]) value = result[sqlrow][sqlcol]
rowprinted=1 rowprinted=1
elif result[sqlrow][0] != last_game: elif result[sqlrow][0] != last_game:
l = gtk.Label(' ') value = ' '
elif 'show' in seats and seats['show'] and result[sqlrow][avgcol] != last_seats: elif 'show' in seats and seats['show'] and result[sqlrow][avgcol] != last_seats:
l = gtk.Label(' ') value = ' '
else: else:
l = gtk.Label(result[sqlrow][sqlcol]) value = result[sqlrow][sqlcol]
rowprinted=1 rowprinted=1
else: else:
l = gtk.Label(' ') l = gtk.Label(' ')
if col == 0: value = ' '
l.set_alignment(xalign=0.0, yalign=0.5) if value and value != -999:
elif col == 1: treerow.append(value)
l.set_alignment(xalign=0.5, yalign=0.5)
else: else:
l.set_alignment(xalign=1.0, yalign=0.5) treerow.append(' ')
eb.add(l) iter = liststore.append(treerow)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
last_game = result[sqlrow][0] last_game = result[sqlrow][0]
last_seats = result[sqlrow][avgcol] last_seats = result[sqlrow][avgcol]
if rowprinted: if rowprinted:
@ -220,50 +290,36 @@ class GuiPositionalStats (threading.Thread):
# blank row between main stats and totals: # blank row between main stats and totals:
col = 0 col = 0
if(row%2 == 0): treerow = [' ' for x in self.posncols]
bgcolor = "white" iter = liststore.append(treerow)
else:
bgcolor = "lightgrey"
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
l = gtk.Label(' ')
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
row = row + 1 row = row + 1
for sqlrow in range(rows): for sqlrow in range(rows):
if(row%2 == 0): treerow = []
bgcolor = "white"
else:
bgcolor = "lightgrey"
for col,colname in enumerate(self.posncols): for col,colname in enumerate(self.posncols):
if colname in colnames: if colname in colnames:
sqlcol = colnames.index(colname) sqlcol = colnames.index(colname)
elif colname != "plposition": elif colname != "plposition":
continue continue
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
if colname == 'plposition': if colname == 'plposition':
l = gtk.Label('Totals') l = gtk.Label('Totals')
value = 'Totals'
elif result[sqlrow][sqlcol]: elif result[sqlrow][sqlcol]:
l = gtk.Label(result[sqlrow][sqlcol]) l = gtk.Label(result[sqlrow][sqlcol])
value = result[sqlrow][sqlcol]
else: else:
l = gtk.Label(' ') l = gtk.Label(' ')
if col == 0: value = ' '
l.set_alignment(xalign=0.0, yalign=0.5) if value and value != -999:
elif col == 1: treerow.append(value)
l.set_alignment(xalign=0.5, yalign=0.5)
else: else:
l.set_alignment(xalign=1.0, yalign=0.5) treerow.append(' ')
eb.add(l) iter = liststore.append(treerow)
self.stats_table.attach(eb, col, col+1, row+1, row+2, yoptions=gtk.SHRINK)
l.show()
eb.show()
row = row + 1 row = row + 1
vbox.show_all()
self.db.db.rollback() self.db.rollback()
print "Positional Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox): #end def fillStatsFrame(self, vbox):
def refineQuery(self, query, playerids, sitenos, limits, seats, dates): def refineQuery(self, query, playerids, sitenos, limits, seats, dates):

View File

@ -25,7 +25,7 @@ from numpy import diff, nonzero
import Card import Card
import fpdb_import import fpdb_import
import fpdb_db import Database
import Filters import Filters
import FpdbSQLQueries import FpdbSQLQueries
@ -33,21 +33,20 @@ class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, debug=True): def __init__(self, config, querylist, debug=True):
self.debug = debug self.debug = debug
self.conf = config self.conf = config
self.sql = querylist
self.MYSQL_INNODB = 2 self.MYSQL_INNODB = 2
self.PGSQL = 3 self.PGSQL = 3
self.SQLITE = 4 self.SQLITE = 4
# create new db connection to avoid conflicts with other threads # create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db() self.db = Database.Database(self.conf, sql=self.sql)
self.db.do_connect(self.conf)
self.cursor = self.db.cursor self.cursor = self.db.cursor
self.sql = querylist
settings = {} settings = {}
settings.update(config.get_db_parameters()) settings.update(self.conf.get_db_parameters())
settings.update(config.get_tv_parameters()) settings.update(self.conf.get_tv_parameters())
settings.update(config.get_import_parameters()) settings.update(self.conf.get_import_parameters())
settings.update(config.get_default_paths()) settings.update(self.conf.get_default_paths())
# text used on screen stored here so that it can be configured # text used on screen stored here so that it can be configured
self.filterText = {'handhead':'Hand Breakdown for all levels listed above' self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
@ -66,7 +65,7 @@ class GuiSessionViewer (threading.Thread):
"Button2" : True "Button2" : True
} }
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display) self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
self.filters.registerButton2Name("_Refresh") self.filters.registerButton2Name("_Refresh")
self.filters.registerButton2Callback(self.refreshStats) self.filters.registerButton2Callback(self.refreshStats)
@ -195,7 +194,7 @@ class GuiSessionViewer (threading.Thread):
flags = [True] flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats) self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats)
self.db.db.commit() self.db.rollback()
print "Stats page displayed in %4.2f seconds" % (time() - starttime) print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox): #end def fillStatsFrame(self, vbox):

View File

@ -159,6 +159,50 @@
<location seat="9" x="70" y="53"> </location> <location seat="9" x="70" y="53"> </location>
</layout> </layout>
</site> </site>
<site enabled="False"
site_name="Win2day"
table_finder="Win2day.exe"
screen_name="YOUR SCREEN NAME HERE"
site_path=""
HH_path=""
decoder="everleaf_decode_table"
converter="Win2dayToFpdb"
supported_games="holdem">
<layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location>
<location seat="2" x="650" y="230"> </location>
<location seat="3" x="650" y="385"> </location>
<location seat="4" x="588" y="425"> </location>
<location seat="5" x="92" y="425"> </location>
<location seat="6" x="0" y="373"> </location>
<location seat="7" x="0" y="223"> </location>
<location seat="8" x="25" y="50"> </location>
</layout>
<layout fav_seat="0" height="547" max="6" width="794">
<location seat="1" x="640" y="58"> </location>
<location seat="2" x="654" y="288"> </location>
<location seat="3" x="615" y="424"> </location>
<location seat="4" x="70" y="421"> </location>
<location seat="5" x="0" y="280"> </location>
<location seat="6" x="70" y="58"> </location>
</layout>
<layout fav_seat="0" height="547" max="2" width="794">
<location seat="1" x="651" y="288"> </location>
<location seat="2" x="10" y="288"> </location>
</layout>
<layout fav_seat="0" height="547" max="9" width="794">
<location seat="1" x="634" y="38"> </location>
<location seat="2" x="667" y="184"> </location>
<location seat="3" x="667" y="321"> </location>
<location seat="4" x="667" y="445"> </location>
<location seat="5" x="337" y="459"> </location>
<location seat="6" x="0" y="400"> </location>
<location seat="7" x="0" y="322"> </location>
<location seat="8" x="0" y="181"> </location>
<location seat="9" x="70" y="53"> </location>
</layout>
</site>
</supported_sites> </supported_sites>
<supported_games> <supported_games>
@ -292,6 +336,7 @@
<hhc site="PokerStars" converter="PokerStarsToFpdb"/> <hhc site="PokerStars" converter="PokerStarsToFpdb"/>
<hhc site="Full Tilt Poker" converter="FulltiltToFpdb"/> <hhc site="Full Tilt Poker" converter="FulltiltToFpdb"/>
<hhc site="Everleaf" converter="EverleafToFpdb"/> <hhc site="Everleaf" converter="EverleafToFpdb"/>
<hhc site="Win2day" converter="Win2dayToFpdb"/>
</hhcs> </hhcs>
<supported_databases> <supported_databases>
@ -300,4 +345,3 @@
</FreePokerToolsConfig> </FreePokerToolsConfig>

View File

@ -29,12 +29,16 @@ Main for FreePokerTools HUD.
# Standard Library modules # Standard Library modules
import sys import sys
# redirect the stderr
errorfile = open('HUD-error.txt', 'w', 0)
sys.stderr = errorfile
import os import os
import Options
(options, sys.argv) = Options.fpdb_options()
if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile
import thread import thread
import time import time
import string import string
@ -59,7 +63,7 @@ class HUD_main(object):
def __init__(self, db_name = 'fpdb'): def __init__(self, db_name = 'fpdb'):
self.db_name = db_name self.db_name = db_name
self.config = Configuration.Config() self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.hud_dict = {} self.hud_dict = {}
# a thread to read stdin # a thread to read stdin
@ -198,17 +202,12 @@ class HUD_main(object):
self.db_connection.connection.rollback() self.db_connection.connection.rollback()
if __name__== "__main__": if __name__== "__main__":
sys.stderr.write("HUD_main starting\n")
# database name can be passed on command line sys.stderr.write("HUD_main starting\n")
try: sys.stderr.write("Using db name = %s\n" % (options.dbname))
db_name = sys.argv[1]
except:
db_name = 'fpdb'
sys.stderr.write("Using db name = %s\n" % (db_name))
# start the HUD_main object # start the HUD_main object
hm = HUD_main(db_name = db_name) hm = HUD_main(db_name = options.dbname)
# start the event loop # start the event loop
gtk.main() gtk.main()

File diff suppressed because it is too large Load Diff

View File

@ -28,91 +28,54 @@ import codecs
from decimal import Decimal from decimal import Decimal
import operator import operator
from xml.dom.minidom import Node from xml.dom.minidom import Node
# from pokereval import PokerEval
import time import time
import datetime import datetime
import gettext
#from pokerengine.pokercards import *
# provides letter2name{}, letter2names{}, visible_card(), not_visible_card(), is_visible(), card_value(), class PokerCards
# but it's probably not installed so here are the ones we may want:
letter2name = {
'A': 'Ace',
'K': 'King',
'Q': 'Queen',
'J': 'Jack',
'T': 'Ten',
'9': 'Nine',
'8': 'Eight',
'7': 'Seven',
'6': 'Six',
'5': 'Five',
'4': 'Four',
'3': 'Trey',
'2': 'Deuce'
}
letter2names = {
'A': 'Aces',
'K': 'Kings',
'Q': 'Queens',
'J': 'Jacks',
'T': 'Tens',
'9': 'Nines',
'8': 'Eights',
'7': 'Sevens',
'6': 'Sixes',
'5': 'Fives',
'4': 'Fours',
'3': 'Treys',
'2': 'Deuces'
}
import gettext import gettext
gettext.install('myapplication') gettext.install('fpdb')
class HandHistoryConverter(): class HandHistoryConverter():
READ_CHUNK_SIZE = 10000 # bytes to read at a time from file (in tail mode) READ_CHUNK_SIZE = 10000 # bytes to read at a time from file (in tail mode)
def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False): def __init__(self, in_path = '-', out_path = '-', sitename = None, follow=False, index=0):
logging.info("HandHistory init called") logging.info("HandHistory init")
# default filetype and codepage. Subclasses should set these properly. # default filetype and codepage. Subclasses should set these properly.
self.filetype = "text" self.filetype = "text"
self.codepage = "utf8" self.codepage = "utf8"
self.index = 0
self.in_path = in_path self.in_path = in_path
self.out_path = out_path self.out_path = out_path
if self.out_path == '-':
# write to stdout self.processedHands = []
if in_path == '-':
self.in_fh = sys.stdin
if out_path == '-':
self.out_fh = sys.stdout self.out_fh = sys.stdout
else: else:
# TODO: out_path should be sanity checked before opening. Perhaps in fpdb_import? # TODO: out_path should be sanity checked.
# I'm not sure what we're looking for, although we don't want out_path==in_path!='-' self.out_fh = open(self.out_path, 'w')
self.out_fh = open(self.out_path, 'w') # doomswitch is now on :|
self.sitename = sitename self.sitename = sitename
self.follow = follow self.follow = follow
self.compiledPlayers = set() self.compiledPlayers = set()
self.maxseats = 10 self.maxseats = 10
def __str__(self): def __str__(self):
#TODO : I got rid of most of the hhdir stuff. return """
tmp = "HandHistoryConverter: '%s'\n" % (self.sitename) HandHistoryConverter: '%(sitename)s'
#tmp = tmp + "\thhbase: '%s'\n" % (self.hhbase) filetype: '%(filetype)s'
#tmp = tmp + "\thhdir: '%s'\n" % (self.hhdir) in_path: '%(in_path)s'
tmp = tmp + "\tfiletype: '%s'\n" % (self.filetype) out_path: '%(out_path)s'
tmp = tmp + "\tinfile: '%s'\n" % (self.in_path) """ % { 'sitename':self.sitename, 'filetype':self.filetype, 'in_path':self.in_path, 'out_path':self.out_path }
tmp = tmp + "\toutfile: '%s'\n" % (self.out_path)
#tmp = tmp + "\tgametype: '%s'\n" % (self.gametype[0])
#tmp = tmp + "\tgamebase: '%s'\n" % (self.gametype[1])
#tmp = tmp + "\tlimit: '%s'\n" % (self.gametype[2])
#tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4])
return tmp
def start(self): def start(self):
"""process a hand at a time from the input specified by in_path. """process a hand at a time from the input specified by in_path.
If in follow mode, wait for more data to turn up. If in follow mode, wait for more data to turn up.
Otherwise, finish at eof... Otherwise, finish at eof.
""" """
starttime = time.time() starttime = time.time()
@ -129,7 +92,7 @@ Otherwise, finish at eof...
handsList = self.allHandsAsList() handsList = self.allHandsAsList()
logging.info("Parsing %d hands" % len(handsList)) logging.info("Parsing %d hands" % len(handsList))
for handText in handsList: for handText in handsList:
self.processHand(handText) self.processedHands.append(self.processHand(handText))
numHands= len(handsList) numHands= len(handsList)
endtime = time.time() endtime = time.time()
print "read %d hands in %.3f seconds" % (numHands, endtime - starttime) print "read %d hands in %.3f seconds" % (numHands, endtime - starttime)
@ -213,6 +176,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
def processHand(self, handText): def processHand(self, handText):
gametype = self.determineGameType(handText) gametype = self.determineGameType(handText)
logging.debug("gametype %s" % gametype) logging.debug("gametype %s" % gametype)
hand = None
if gametype is None: if gametype is None:
l = None l = None
gametype = "unmatched" gametype = "unmatched"
@ -224,9 +188,7 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
base = gametype['base'] base = gametype['base']
limit = gametype['limitType'] limit = gametype['limitType']
l = [type] + [base] + [limit] l = [type] + [base] + [limit]
hand = None
if l in self.readSupportedGames(): if l in self.readSupportedGames():
hand = None
if gametype['base'] == 'hold': if gametype['base'] == 'hold':
logging.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)") logging.debug("hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext)")
hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText) hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handText)
@ -238,7 +200,9 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py.
logging.info("Unsupported game type: %s" % gametype) logging.info("Unsupported game type: %s" % gametype)
if hand: if hand:
# print hand
hand.writeHand(self.out_fh) hand.writeHand(self.out_fh)
return hand
else: else:
logging.info("Unsupported game type: %s" % gametype) logging.info("Unsupported game type: %s" % gametype)
# TODO: pity we don't know the HID at this stage. Log the entire hand? # TODO: pity we don't know the HID at this stage. Log the entire hand?
@ -306,6 +270,11 @@ or None if we fail to get the info """
def readCollectPot(self, hand): abstract def readCollectPot(self, hand): abstract
def readShownCards(self, hand): abstract def readShownCards(self, hand): abstract
# Some sites do odd stuff that doesn't fall in to the normal HH parsing.
# e.g., FTP doesn't put mixed game info in the HH, but puts in in the
# file name. Use readOther() to clean up those messes.
def readOther(self, hand): pass
# Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated # Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated
# an inheriting class can calculate it for the specific site if need be. # an inheriting class can calculate it for the specific site if need be.
def getRake(self, hand): def getRake(self, hand):
@ -349,6 +318,7 @@ or None if we fail to get the info """
self.filetype = filetype self.filetype = filetype
self.codepage = codepage self.codepage = codepage
#This function doesn't appear to be used
def splitFileIntoHands(self): def splitFileIntoHands(self):
hands = [] hands = []
self.obs = self.obs.strip() self.obs = self.obs.strip()
@ -370,7 +340,9 @@ or None if we fail to get the info """
else: else:
logging.debug("Opening %s with %s" % (self.in_path, self.codepage)) logging.debug("Opening %s with %s" % (self.in_path, self.codepage))
in_fh = codecs.open(self.in_path, 'r', self.codepage) in_fh = codecs.open(self.in_path, 'r', self.codepage)
in_fh.seek(self.index)
self.obs = in_fh.read() self.obs = in_fh.read()
self.index = in_fh.tell()
in_fh.close() in_fh.close()
elif(self.filetype == "xml"): elif(self.filetype == "xml"):
try: try:
@ -379,10 +351,39 @@ or None if we fail to get the info """
except: except:
traceback.print_exc(file=sys.stderr) traceback.print_exc(file=sys.stderr)
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy
if hand.gametype['base'] == 'stud':
if mo <= 8: return 8
else: return mo
if hand.gametype['base'] == 'draw':
if mo <= 6: return 6
else: return mo
if mo == 2: return 2
if mo <= 6: return 6
return 10
def maxOccSeat(self, hand):
max = 0
for player in hand.players:
if player[0] > max: max = player[0]
return max
def getStatus(self): def getStatus(self):
#TODO: Return a status of true if file processed ok #TODO: Return a status of true if file processed ok
return True return True
def getProcessedHands(self):
return self.processedHands
def getProcessedFile(self): def getProcessedFile(self):
return self.out_path return self.out_path
def getLastCharacterRead(self):
return self.index

View File

@ -34,10 +34,13 @@ import gobject
# FreePokerTools modules # FreePokerTools modules
from Mucked import Aux_Window from Mucked import Aux_Window
from Mucked import Seat_Window
from Mucked import Aux_Seats
class Hello(Aux_Window): class Hello(Aux_Window):
"""A 'Hello World' Aux_Window demo.""" """A 'Hello World' Aux_Window demo."""
def create(self): def create(self):
print "creating Hello"
# This demo simply creates a label in a window. # This demo simply creates a label in a window.
self.container = gtk.Window() self.container = gtk.Window()
self.container.add(gtk.Label("Hello World")) self.container.add(gtk.Label("Hello World"))
@ -99,15 +102,18 @@ class Hello_plus(Aux_Window):
# hands played that was updated in the "update_data()" function. # hands played that was updated in the "update_data()" function.
self.label.set_text("Hello %s\nYou have played %d hands\n on %s." % (self.hero, self.hands_played, self.site)) self.label.set_text("Hello %s\nYou have played %d hands\n on %s." % (self.hero, self.hands_played, self.site))
class Hello_Menu(Aux_Window): class Hello_Seats(Aux_Seats):
"""A 'Hello World' Aux_Window demo.""" """A 'Hello World' Seat_Window demo."""
def create(self):
# This demo puts a menu item on the HUD mainwindow.
self.item = gtk.MenuItem('Print cards')
self.hud.menu.append(self.item)
self.item.connect("activate", self.print_cards)
self.item.show()
def print_cards(self, *args): def create_contents(self, container, i):
# callback for the menu item container.label = gtk.Label("empty")
print "cards =", self.hud.cards container.add(container.label)
container.show_all()
def update_contents(self, container, i):
if i == "common": return
id = self.get_id_from_seat(i)
if id == None:
container.label.set_text("empty")
else:
container.label.set_text("player = %s" % self.hud.stat_dict[id]['screen_name'])

View File

@ -43,7 +43,7 @@ import Configuration
import Stats import Stats
import Mucked import Mucked
import Database import Database
import HUD_main #import HUD_main
def importName(module_name, name): def importName(module_name, name):
"""Import a named object 'name' from module 'module_name'.""" """Import a named object 'name' from module 'module_name'."""
@ -445,10 +445,10 @@ class Stat_Window:
Stats.do_tip(e_box[r][c], 'stuff') Stats.do_tip(e_box[r][c], 'stuff')
if usegtkframes: if usegtkframes:
grid.attach(self.frame[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) grid.attach(self.frame[r][c], c, c+1, r, r+1, xpadding = game.xpad, ypadding = game.ypad)
self.frame[r][c].add(e_box[r][c]) self.frame[r][c].add(e_box[r][c])
else: else:
grid.attach(e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0) grid.attach(e_box[r][c], c, c+1, r, r+1, xpadding = game.xpad, ypadding = game.ypad)
label[r].append( gtk.Label('xxx') ) label[r].append( gtk.Label('xxx') )
if usegtkframes: if usegtkframes:

View File

@ -35,28 +35,20 @@ import gobject
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
import Database import Database
import Card
class Aux_Window: class Aux_Window(object):
def __init__(self, hud, params, config): def __init__(self, hud, params, config):
self.hud = hud self.hud = hud
self.params = params self.params = params
self.config = config self.config = config
def update_data(self, *args): # Override these methods as needed
pass def update_data(self, *args): pass
def update_gui(self, *args): pass
def update_gui(self, *args): def create(self, *args): pass
pass def relocate(self, *args): pass
def save_layout(self, *args): pass
def create(self, *args):
pass
def relocate(self, *args):
pass
def save_layout(self, *args):
pass
def destroy(self): def destroy(self):
try: try:
self.container.destroy() self.container.destroy()
@ -67,27 +59,40 @@ class Aux_Window:
# Some utility routines useful for Aux_Windows # Some utility routines useful for Aux_Windows
# #
def get_card_images(self): def get_card_images(self):
card_images = {}
suits = ('S', 'H', 'D', 'C') card_images = 53 * [0]
ranks = ('A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2', 'B') suits = ('s', 'h', 'd', 'c')
ranks = (14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2)
pb = gtk.gdk.pixbuf_new_from_file(self.config.execution_path(self.params['deck'])) pb = gtk.gdk.pixbuf_new_from_file(self.config.execution_path(self.params['deck']))
for j in range(0, 14): for j in range(0, 13):
for i in range(0, 4): for i in range(0, 4):
card_images[Card.cardFromValueSuit(ranks[j], suits[i])] = self.cropper(pb, i, j)
temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42) temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42)
pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0) # also pick out a card back and store in [0]
card_images[(ranks[j], suits[i])] = temp_pb card_images[0] = self.cropper(pb, 2, 13)
return(card_images) return(card_images)
# cards are 30 wide x 42 high # cards are 30 wide x 42 high
def split_cards(self, card): def cropper(self, pb, i, j):
if card == 'xx': return ('B', 'S') """Crop out a card image given an FTP deck and the i, j position."""
return (card[0], card[1].upper()) temp_pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, pb.get_has_alpha(), pb.get_bits_per_sample(), 30, 42)
pb.copy_area(30*j, 42*i, 30, 42, temp_pb, 0, 0)
return temp_pb
def has_cards(self, cards): def has_cards(self, cards):
"""Returns the number of cards in the list."""
n = 0
for c in cards: for c in cards:
if c in set('shdc'): return True if c != None and c > 0: n = n + 1
return False return n
def get_id_from_seat(self, seat):
"""Determine player id from seat number, given stat_dict."""
for id, dict in self.hud.stat_dict.iteritems():
if seat == dict['seat']:
return id
return None
class Stud_mucked(Aux_Window): class Stud_mucked(Aux_Window):
def __init__(self, hud, config, params): def __init__(self, hud, config, params):
@ -111,6 +116,7 @@ class Stud_mucked(Aux_Window):
self.container = gtk.Window() self.container = gtk.Window()
self.vbox = gtk.VBox() self.vbox = gtk.VBox()
self.container.add(self.vbox) self.container.add(self.vbox)
self.container.set_title(self.hud.table.name)
self.mucked_list.create(self.vbox) self.mucked_list.create(self.vbox)
self.mucked_cards.create(self.vbox) self.mucked_cards.create(self.vbox)
@ -207,7 +213,9 @@ class Stud_list:
# find the hero's seat from the stat_dict # find the hero's seat from the stat_dict
for stat in self.parent.hud.stat_dict.itervalues(): for stat in self.parent.hud.stat_dict.itervalues():
if stat['screen_name'] == hero: if stat['screen_name'] == hero:
return self.parent.hud.cards[stat['seat']][0:6] return Card.valueSuitFromCard(self.parent.hud.cards[stat['seat']][0]) +\
Card.valueSuitFromCard(self.parent.hud.cards[stat['seat']][1]) +\
Card.valueSuitFromCard(self.parent.hud.cards[stat['seat']][2])
return "xxxxxx" return "xxxxxx"
def update_gui(self, new_hand_id): def update_gui(self, new_hand_id):
@ -240,7 +248,7 @@ class Stud_cards:
for r in range(0, self.rows): for r in range(0, self.rows):
for c in range(0, self.cols): for c in range(0, self.cols):
self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[('B', 'S')]) self.seen_cards[(c, r)] = gtk.image_new_from_pixbuf(self.card_images[(0)])
self.eb[(c, r)]= gtk.EventBox() self.eb[(c, r)]= gtk.EventBox()
# set up the contents for the cells # set up the contents for the cells
@ -287,11 +295,11 @@ class Stud_cards:
self.clear() self.clear()
for c, cards in self.parent.hud.cards.iteritems(): for c, cards in self.parent.hud.cards.iteritems():
self.grid_contents[(1, c - 1)].set_text(self.get_screen_name(c)) self.grid_contents[(1, c - 1)].set_text(self.get_screen_name(c))
for i in ((0, cards[0:2]), (1, cards[2:4]), (2, cards[4:6]), (3, cards[6:8]), for i in ((0, cards[0]), (1, cards[1]), (2, cards[2]), (3, cards[3]),
(4, cards[8:10]), (5, cards[10:12]), (6, cards[12:14])): (4, cards[4]), (5, cards[5]), (6, cards[6])):
if not i[1] == "xx": if not i[1] == 0:
self.seen_cards[(i[0], c - 1)]. \ self.seen_cards[(i[0], c - 1)]. \
set_from_pixbuf(self.card_images[self.parent.split_cards(i[1])]) set_from_pixbuf(self.card_images[i[1]])
## action in tool tips for 3rd street cards ## action in tool tips for 3rd street cards
for c in (0, 1, 2): for c in (0, 1, 2):
for r in range(0, self.rows): for r in range(0, self.rows):
@ -314,90 +322,155 @@ class Stud_cards:
for r in range(0, self.rows): for r in range(0, self.rows):
self.grid_contents[(1, r)].set_text(" ") self.grid_contents[(1, r)].set_text(" ")
for c in range(0, 7): for c in range(0, 7):
self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[0])
self.eb[(c, r)].set_tooltip_text('') self.eb[(c, r)].set_tooltip_text('')
class Flop_Mucked(Aux_Window): class Seat_Window(gtk.Window):
"""Aux_Window class for displaying mucked cards for flop games.""" """Subclass gtk.Window for the seat windows."""
class Aux_Seats(Aux_Window):
"""A super class to display an aux_window at each seat."""
def __init__(self, hud, config, params): def __init__(self, hud, config, params):
self.hud = hud # hud object that this aux window supports self.hud = hud # hud object that this aux window supports
self.config = config # configuration object for this aux window to use self.config = config # configuration object for this aux window to use
self.params = params # dict aux params from config self.params = params # dict aux params from config
self.positions = {} # dict of window positions self.positions = {} # dict of window positions
# self.rel_positions = {} # dict of window positions, relative to the table origin self.displayed = False # the seat windows are displayed
self.displayed_cards = False self.uses_timer = False # the Aux_seats object uses a timer to control hiding
self.timer_on = False # bool = Ture if the timeout for removing the cards is on self.timer_on = False # bool = Ture if the timeout for removing the cards is on
self.card_images = self.get_card_images()
# placeholders that should be overridden--so we don't throw errors
def create_contents(self): pass
def update_contents(self): pass
def create(self): def create(self):
self.adj = self.hud.adj_seats(0, self.config) self.adj = self.hud.adj_seats(0, self.config) # move adj_seats to aux and get rid of it in Hud.py
loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max)) loc = self.config.get_aux_locations(self.params['name'], int(self.hud.max))
self.m_windows = {} # windows to put the card images in self.m_windows = {} # windows to put the card images in
self.eb = {} # event boxes so we can interact with the mucked cards
self.seen_cards = {} # image objects to stash the cards in
for i in (range(1, self.hud.max + 1) + ['common']): for i in (range(1, self.hud.max + 1) + ['common']):
if i == 'common': if i == 'common':
(x, y) = self.params['layout'][self.hud.max].common (x, y) = self.params['layout'][self.hud.max].common
else: else:
(x, y) = loc[self.adj[i]] (x, y) = loc[self.adj[i]]
self.m_windows[i] = gtk.Window() self.m_windows[i] = Seat_Window()
self.m_windows[i].set_decorated(False) self.m_windows[i].set_decorated(False)
self.m_windows[i].set_property("skip-taskbar-hint", True) self.m_windows[i].set_property("skip-taskbar-hint", True)
self.m_windows[i].set_transient_for(self.hud.main_window) self.m_windows[i].set_transient_for(self.hud.main_window)
self.m_windows[i].set_focus_on_map(False) self.m_windows[i].set_focus_on_map(False)
self.eb[i] = gtk.EventBox()
self.eb[i].connect("button_press_event", self.button_press_cb)
self.m_windows[i].connect("configure_event", self.configure_event_cb, i) self.m_windows[i].connect("configure_event", self.configure_event_cb, i)
self.m_windows[i].add(self.eb[i])
self.seen_cards[i] = gtk.image_new_from_pixbuf(self.card_images[('B', 'H')])
self.eb[i].add(self.seen_cards[i])
self.positions[i] = (int(x) + self.hud.table.x, int(y) + self.hud.table.y) self.positions[i] = (int(x) + self.hud.table.x, int(y) + self.hud.table.y)
# self.rel_positions[i] = (int(x), int(y))
self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) self.m_windows[i].move(self.positions[i][0], self.positions[i][1])
if self.params.has_key('opacity'):
self.m_windows[i].set_opacity(float(self.params['opacity'])) self.m_windows[i].set_opacity(float(self.params['opacity']))
# the create_contents method is supplied by the subclass
self.create_contents(self.m_windows[i], i)
self.m_windows[i].show_all() self.m_windows[i].show_all()
if self.uses_timer:
self.m_windows[i].hide() self.m_windows[i].hide()
def update_gui(self, new_hand_id): def update_gui(self, new_hand_id):
"""Prepare and show the mucked cards.""" """Update the gui, LDO."""
if self.displayed_cards: for i in self.m_windows.keys():
self.hide_mucked_cards() self.update_contents(self.m_windows[i], i)
self.displayed_cards = False
for (i, cards) in self.hud.cards.iteritems(): # Methods likely to be of use for any Seat_Window implementation
if self.has_cards(cards): def destroy(self):
"""Destroy all of the seat windows."""
for i in self.m_windows.keys():
self.m_windows[i].destroy()
del(self.m_windows[i])
# Methods likely to be useful for mucked card windows (or similar) only
def hide(self):
"""Hide the seat windows."""
for (i, w) in self.m_windows.iteritems():
w.hide()
self.displayed = False
def save_layout(self, *args):
"""Save new layout back to the aux element in the config file."""
new_locs = {}
# print "adj =", self.adj
for (i, pos) in self.positions.iteritems():
if i != 'common':
new_locs[self.adj[int(i)]] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
else:
new_locs[i] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
self.config.edit_aux_layout(self.params['name'], self.hud.max, locations = new_locs)
def configure_event_cb(self, widget, event, i, *args):
self.positions[i] = widget.get_position()
# self.rel_positions[i] = (self.positions[i][0] - self.hud.table.x, self.positions[i][1] - self.hud.table.y)
class Flop_Mucked(Aux_Seats):
"""Aux_Window class for displaying mucked cards for flop games."""
def __init__(self, hud, config, params):
super(Flop_Mucked, self).__init__(hud, config, params)
self.card_images = self.get_card_images()
self.uses_timer = True # this Aux_seats object uses a timer to control hiding
def create_contents(self, container, i):
"""Create the widgets for showing the contents of the Aux_seats window."""
container.eb = gtk.EventBox()
container.eb.connect("button_press_event", self.button_press_cb)
container.add(container.eb)
container.seen_cards = gtk.image_new_from_pixbuf(self.card_images[0])
container.eb.add(container.seen_cards)
def update_contents(self, container, i):
if not self.hud.cards.has_key(i): return
cards = self.hud.cards[i]
n_cards = self.has_cards(cards)
if n_cards > 1:
# scratch is a working pixbuf, used to assemble the image # scratch is a working pixbuf, used to assemble the image
scratch = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, scratch = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
int(self.params['card_wd'])*len(cards)/2, int(self.params['card_wd'])*n_cards,
int(self.params['card_ht'])) int(self.params['card_ht']))
x = 0 # x coord where the next card starts in scratch x = 0 # x coord where the next card starts in scratch
for card in [cards[k:k+2] for k in xrange(0, len(cards), 2)]: for card in cards:
# concatenate each card image to scratch # concatenate each card image to scratch
self.card_images[self.split_cards(card)].copy_area(0, 0, if card == None or card ==0:
break
self.card_images[card].copy_area(0, 0,
int(self.params['card_wd']), int(self.params['card_ht']), int(self.params['card_wd']), int(self.params['card_ht']),
scratch, x, 0) scratch, x, 0)
x = x + int(self.params['card_wd']) x = x + int(self.params['card_wd'])
self.seen_cards[i].set_from_pixbuf(scratch) container.seen_cards.set_from_pixbuf(scratch)
# self.m_windows[i].show_all() container.resize(1,1)
self.m_windows[i].resize(1,1) container.show()
self.m_windows[i].show() container.move(self.positions[i][0], self.positions[i][1]) # here is where I move back
self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) # here is where I move back self.displayed = True
self.displayed_cards = True if i != "common":
id = self.get_id_from_seat(i)
self.m_windows[i].eb.set_tooltip_text(self.hud.stat_dict[id]['screen_name'])
for stats in self.hud.stat_dict.itervalues(): def update_gui(self, new_hand_id):
self.eb[stats['seat']].set_tooltip_text(stats['screen_name']) """Prepare and show the mucked cards."""
if self.displayed: self.hide()
if self.displayed_cards and float(self.params['timeout']) > 0: # See how many players showed a hand. Skip if only 1 shows (= hero)
n_sd = 0
for (i, cards) in self.hud.cards.iteritems():
n_cards = self.has_cards(cards)
if n_cards > 0 and i != 'common':
n_sd = n_sd + 1
if n_sd < 2:
print "skipping, n_sd =", n_sd
return
super(Flop_Mucked, self).update_gui(new_hand_id)
if self.displayed and float(self.params['timeout']) > 0:
self.timer_on = True self.timer_on = True
gobject.timeout_add(int(1000*float(self.params['timeout'])), self.timed_out) gobject.timeout_add(int(1000*float(self.params['timeout'])), self.timed_out)
def destroy(self):
"""Destroy all of the mucked windows."""
for w in self.m_windows.values():
w.destroy()
def timed_out(self): def timed_out(self):
# this is the callback from the timeout # this is the callback from the timeout
@ -406,15 +479,9 @@ class Flop_Mucked(Aux_Window):
if not self.timer_on: if not self.timer_on:
return False return False
else: else:
self.hide_mucked_cards() self.hide()
return False return False
def hide_mucked_cards(self):
"""Hide the mucked card windows."""
for (i, w) in self.m_windows.iteritems():
w.hide()
self.displayed_cards = False
def button_press_cb(self, widget, event, *args): def button_press_cb(self, widget, event, *args):
"""Handle button clicks in the event boxes.""" """Handle button clicks in the event boxes."""
@ -432,56 +499,14 @@ class Flop_Mucked(Aux_Window):
self.timer_on = False self.timer_on = False
else: else:
self.timer_on = False self.timer_on = False
self.hide_mucked_cards() self.hide()
elif event.button == 1: # left button event elif event.button == 1: # left button event
window = widget.get_parent() window = widget.get_parent()
window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time) window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
def configure_event_cb(self, widget, event, i, *args):
self.positions[i] = widget.get_position()
# self.rel_positions[i] = (self.positions[i][0] - self.hud.table.x, self.positions[i][1] - self.hud.table.y)
def expose_all(self): def expose_all(self):
for (i, cards) in self.hud.cards.iteritems(): for (i, cards) in self.hud.cards.iteritems():
self.m_windows[i].show() self.m_windows[i].show()
self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) # here is where I move back self.m_windows[i].move(self.positions[i][0], self.positions[i][1]) # here is where I move back
self.displayed_cards = True self.displayed = True
def save_layout(self, *args):
"""Save new layout back to the aux element in the config file."""
new_locs = {}
# print "adj =", self.adj
for (i, pos) in self.positions.iteritems():
if i != 'common':
new_locs[self.adj[int(i)]] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
else:
new_locs[i] = (pos[0] - self.hud.table.x, pos[1] - self.hud.table.y)
self.config.edit_aux_layout(self.params['name'], self.hud.max, locations = new_locs)
if __name__== "__main__":
def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit() # used only for testing
def process_new_hand(source, condition, db_connection): #callback from stdin watch -- testing only
# there is a new hand_id to be processed
# just read it and pass it to update
new_hand_id = sys.stdin.readline()
new_hand_id = new_hand_id.rstrip() # remove trailing whitespace
m.update_data(new_hand_id, db_connection)
m.update_gui(new_hand_id)
return(True)
config = Configuration.Config()
db_connection = Database.Database(config, 'fpdb', '')
main_window = gtk.Window()
main_window.set_keep_above(True)
main_window.connect("destroy", destroy)
aux_to_call = "stud_mucked"
aux_params = config.get_aux_parameters(aux_to_call)
m = eval("%s(main_window, None, config, aux_params)" % aux_params['class'])
s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand, db_connection)
gtk.main()

45
pyfpdb/Options.py Normal file
View File

@ -0,0 +1,45 @@
#!/usr/bin/python
#Copyright 2008 Ray E. Barker
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#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 Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import os
import sys
from optparse import OptionParser
def fpdb_options():
"""Process command line options for fpdb and HUD_main."""
parser = OptionParser()
parser.add_option("-x", "--errorsToConsole",
action="store_true",
help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName",
dest="dbname", default="fpdb",
help="Overrides the default database name")
parser.add_option("-c", "--configFile",
dest="config", default=None,
help="Specifies a configuration file.")
(options, sys.argv) = parser.parse_args()
return (options, sys.argv)
if __name__== "__main__":
(options, sys.argv) = fpdb_options()
print "errorsToConsole =", options.errorsToConsole
print "database name =", options.dbname
print "config file =", options.config
print "press enter to end"
sys.stdin.readline()

View File

@ -18,6 +18,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
######################################################################## ########################################################################
# TODO: straighten out discards for draw games
import sys import sys
from HandHistoryConverter import * from HandHistoryConverter import *
@ -25,22 +26,41 @@ from HandHistoryConverter import *
class PokerStars(HandHistoryConverter): class PokerStars(HandHistoryConverter):
############################################################
# Class Variables
# Static regexes # Static regexes
re_GameInfo = re.compile("PokerStars Game #(?P<HID>[0-9]+):\s+(HORSE)? \(?(?P<GAME>Hold\'em|Razz|7 Card Stud|7 Card Stud Hi/Lo|Omaha|Omaha Hi/Lo|Badugi) (?P<LIMIT>No Limit|Limit|Pot Limit),? \(?(?P<CURRENCY>\$|)?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\) - (?P<DATETIME>.*$)", re.MULTILINE) re_GameInfo = re.compile("""PokerStars\sGame\s\#(?P<HID>[0-9]+):\s+
(Tournament\s\#(?P<TOURNO>\d+),\s(?P<BUYIN>[\$\+\d\.]+)\s)?
(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
(?P<GAME>Hold\'em|Razz|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball)\s
(?P<LIMIT>No\sLimit|Limit|Pot\sLimit)\)?,?\s
(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?\(?
(?P<CURRENCY>\$|)?
(?P<SB>[.0-9]+)/\$?
(?P<BB>[.0-9]+)\)\s-\s
(?P<DATETIME>.*$)""",
re.MULTILINE|re.VERBOSE)
re_SplitHands = re.compile('\n\n+') re_SplitHands = re.compile('\n\n+')
re_TailSplitHands = re.compile('(\n\n\n+)') re_TailSplitHands = re.compile('(\n\n\n+)')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE) re_HandInfo = re.compile("""^Table\s\'(?P<TABLE>[-\ a-zA-Z\d]+)\'\s
((?P<MAX>\d+)-max\s)?
(?P<PLAY>\(Play\sMoney\)\s)?
(Seat\s\#(?P<BUTTON>\d+)\sis\sthe\sbutton)?""",
re.MULTILINE|re.VERBOSE)
re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE) re_Button = re.compile('Seat #(?P<BUTTON>\d+) is the button', re.MULTILINE)
re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE) re_PlayerInfo = re.compile('^Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[.0-9]+) in chips\)', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)') # self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True): mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'}
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
follow : whether to tail -f the input""" follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="PokerStars", follow=follow) HandHistoryConverter.__init__(self, in_path, out_path, sitename="PokerStars", follow=follow, index=index)
logging.info("Initialising PokerStars converter class") logging.info("Initialising PokerStars converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
@ -53,6 +73,9 @@ follow : whether to tail -f the input"""
players = set([player[1] for player in hand.players]) players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs. # we need to recompile the player regexs.
# TODO: should probably rename re_HeroCards and corresponding method,
# since they are used to find all cards on lines starting with "Dealt to:"
# They still identify the hero.
self.compiledPlayers = players self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re) logging.debug("player_re: " + player_re)
@ -62,32 +85,43 @@ follow : whether to tail -f the input"""
self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r"^%s: posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBoth = re.compile(r"^%s: posts small \& big blinds \[\$? (?P<SBBB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE) self.re_HeroCards = re.compile(r"^Dealt to %s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % player_re, re.MULTILINE)
self.re_Action = re.compile(r"^%s:(?P<ATYPE> bets| checks| raises| calls| folds| discards| stands pat)( \$(?P<BET>[.\d]+))?( to \$(?P<BETTO>[.\d]+))?( (?P<NODISCARDED>\d) cards?( \[(?P<DISCARDED>.+?)\])?)?" % player_re, re.MULTILINE) self.re_Action = re.compile(r"""^%s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat)
(\s\$?(?P<BET>[.\d]+))?(\sto\s\$?(?P<BETTO>[.\d]+))? # the number discarded goes in <BET>
(\scards?(\s\[(?P<DISCARDED>.+?)\])?)?"""
% player_re, re.MULTILINE|re.VERBOSE)
self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE) self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P<CARDS>.*)\]" % player_re, re.MULTILINE)
self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$(?P<POT>[.\d]+)\)(, mucked| with.*|)" % player_re, re.MULTILINE) self.re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %s (\(button\) |\(small blind\) |\(big blind\) )?(collected|showed \[.*\] and won) \(\$?(?P<POT>[.\d]+)\)(, mucked| with.*|)" % player_re, re.MULTILINE)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE) self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE) self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s (\(.*\) )?(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self): def readSupportedGames(self):
return [["ring", "hold", "nl"], return [["ring", "hold", "nl"],
["ring", "hold", "pl"], ["ring", "hold", "pl"],
["ring", "hold", "fl"], ["ring", "hold", "fl"],
["ring", "stud", "fl"], ["ring", "stud", "fl"],
#["ring", "draw", "fl"],
["ring", "omaha", "pl"] ["ring", "draw", "fl"],
["tour", "hold", "nl"],
["tour", "hold", "pl"],
["tour", "hold", "fl"],
["tour", "stud", "fl"],
] ]
def determineGameType(self, handText): def determineGameType(self, handText):
info = {'type':'ring'} # inspect the handText and return the gametype dict
# gametype dict is:
# {'limitType': xxx, 'base': xxx, 'category': xxx}
info = {}
m = self.re_GameInfo.search(handText) m = self.re_GameInfo.search(handText)
if not m: if not m:
return None return None
mg = m.groupdict() mg = m.groupdict()
# translations from captured groups to fpdb info strings
# translations from captured groups to our info strings
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' } limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category games = { # base, category
"Hold'em" : ('hold','holdem'), "Hold'em" : ('hold','holdem'),
@ -96,9 +130,14 @@ follow : whether to tail -f the input"""
'Razz' : ('stud','razz'), 'Razz' : ('stud','razz'),
'7 Card Stud' : ('stud','studhi'), '7 Card Stud' : ('stud','studhi'),
'7 Card Stud Hi/Lo' : ('stud','studhilo'), '7 Card Stud Hi/Lo' : ('stud','studhilo'),
'Badugi' : ('draw','badugi') 'Badugi' : ('draw','badugi'),
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
} }
currencies = { u'':'EUR', '$':'USD', '':'T$' } currencies = { u'':'EUR', '$':'USD', '':'T$' }
# I don't think this is doing what we think. mg will always have all
# the expected keys, but the ones that didn't match in the regex will
# have a value of None. It is OK if it throws an exception when it
# runs across an unknown game or limit or whatever.
if 'LIMIT' in mg: if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']] info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg: if 'GAME' in mg:
@ -109,8 +148,13 @@ follow : whether to tail -f the input"""
info['bb'] = mg['BB'] info['bb'] = mg['BB']
if 'CURRENCY' in mg: if 'CURRENCY' in mg:
info['currency'] = currencies[mg['CURRENCY']] info['currency'] = currencies[mg['CURRENCY']]
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
if 'TOURNO' in mg and mg['TOURNO'] == None:
info['type'] = 'ring'
else:
info['type'] = 'tour'
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info return info
@ -119,14 +163,13 @@ follow : whether to tail -f the input"""
m = self.re_HandInfo.search(hand.handText,re.DOTALL) m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m: if m:
info.update(m.groupdict()) info.update(m.groupdict())
# TODO: Be less lazy and parse maxseats from the HandInfo regex # hand.maxseats = int(m2.group(1))
if m.group('TABLEATTRIBUTES'): else:
m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES')) pass # throw an exception here, eh?
hand.maxseats = int(m2.group(1))
m = self.re_GameInfo.search(hand.handText) m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict()) if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText) # m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict()) # if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info # TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info) logging.debug("readHandInfo: %s" % info)
for key in info: for key in info:
@ -143,6 +186,22 @@ follow : whether to tail -f the input"""
hand.tablename = info[key] hand.tablename = info[key]
if key == 'BUTTON': if key == 'BUTTON':
hand.buttonpos = info[key] hand.buttonpos = info[key]
if key == 'MAX':
hand.maxseats = int(info[key])
if key == 'MIXED':
if info[key] == None: hand.mixed = None
else: hand.mixed = self.mixes[info[key]]
if key == 'TOURNO':
hand.tourNo = info[key]
if key == 'BUYIN':
hand.buyin = info[key]
if key == 'LEVEL':
hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != None:
# hand.currency = 'play' # overrides previously set value
hand.gametype['currency'] = 'play'
def readButton(self, hand): def readButton(self, hand):
m = self.re_Button.search(hand.handText) m = self.re_Button.search(hand.handText)
@ -209,80 +268,117 @@ follow : whether to tail -f the input"""
for a in self.re_PostBB.finditer(hand.handText): for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText): for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB')) hand.addBlind(a.group('PNAME'), 'both', a.group('SBBB'))
# def readHeroCards(self, hand):
# m = self.re_HeroCards.search(hand.handText)
# if(m == None):
# #Not involved in hand
# hand.involved = False
# else:
# hand.hero = m.group('PNAME')
# # "2c, qh" -> set(["2c","qc"])
# # Also works with Omaha hands.
# cards = m.group('NEWCARDS')
# cards = set(cards.split(' '))
# hand.addHoleCards(cards, m.group('PNAME'), shown=False, mucked=False, dealt=True)
def readHeroCards(self, hand): def readHeroCards(self, hand):
m = self.re_HeroCards.search(hand.handText) # streets PREFLOP, PREDRAW, and THIRD are special cases beacause
if(m == None): # we need to grab hero's cards
#Not involved in hand for street in ('PREFLOP', 'DEAL'):
hand.involved = False if street in hand.streets.keys():
else:
hand.hero = m.group('PNAME')
# "2c, qh" -> set(["2c","qc"])
# Also works with Omaha hands.
cards = m.group('NEWCARDS')
cards = set(cards.split(' '))
hand.addHoleCards(cards, m.group('PNAME'))
def readDrawCards(self, hand, street):
logging.debug("readDrawCards")
m = self.re_HeroCards.finditer(hand.streets[street]) m = self.re_HeroCards.finditer(hand.streets[street])
if m == None: for found in m:
hand.involved = False # if m == None:
else: # hand.involved = False
for player in m: # else:
hand.hero = player.group('PNAME') # Only really need to do this once hand.hero = found.group('PNAME')
newcards = player.group('NEWCARDS') newcards = found.group('NEWCARDS').split(' ')
oldcards = player.group('OLDCARDS') hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
if newcards == None:
newcards = set()
else:
newcards = set(newcards.split(' '))
if oldcards == None:
oldcards = set()
else:
oldcards = set(oldcards.split(' '))
hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
for street, text in hand.streets.iteritems():
def readStudPlayerCards(self, hand, street): if not text or street in ('PREFLOP', 'DEAL'): continue # already done these
# See comments of reference implementation in FullTiltToFpdb.py
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street]) m = self.re_HeroCards.finditer(hand.streets[street])
for player in m: for found in m:
#~ logging.debug(player.groupdict()) player = found.group('PNAME')
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS')) if found.group('NEWCARDS') == None:
if oldcards: newcards = []
oldcards = [c.strip() for c in oldcards.split(' ')] else:
if newcards: newcards = found.group('NEWCARDS').split(' ')
newcards = [c.strip() for c in newcards.split(' ')] if found.group('OLDCARDS') == None:
if street=='ANTES': oldcards = []
return else:
elif street=='THIRD': oldcards = found.group('OLDCARDS').split(' ')
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o] if street == 'THIRD' and len(newcards) == 3: # hero in stud game
# others: [o] hand.hero = player
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards) hand.dealt.add(player) # need this for stud??
elif street in ('FOURTH', 'FIFTH', 'SIXTH'): hand.addHoleCards(street, player, closed=newcards[0:2], open=[newcards[2]], shown=False, mucked=False, dealt=False)
# 4th: else:
# hero: [xxo] [o] hand.addHoleCards(street, player, open=newcards, closed=oldcards, shown=False, mucked=False, dealt=False)
# others: [o] [o]
# 5th:
# hero: [xxoo] [o] # def readDrawCards(self, hand, street):
# others: [oo] [o] # logging.debug("readDrawCards")
# 6th: # m = self.re_HeroCards.finditer(hand.streets[street])
# hero: [xxooo] [o] # if m == None:
# others: [ooo] [o] # hand.involved = False
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards) # else:
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now. # for player in m:
elif street=='SEVENTH' and newcards: # hand.hero = player.group('PNAME') # Only really need to do this once
# hero: [xxoooo] [x] # newcards = player.group('NEWCARDS')
# others: not reported. # oldcards = player.group('OLDCARDS')
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards) # if newcards == None:
# newcards = set()
# else:
# newcards = set(newcards.split(' '))
# if oldcards == None:
# oldcards = set()
# else:
# oldcards = set(oldcards.split(' '))
# hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
# def readStudPlayerCards(self, hand, street):
# # See comments of reference implementation in FullTiltToFpdb.py
# logging.debug("readStudPlayerCards")
# m = self.re_HeroCards.finditer(hand.streets[street])
# for player in m:
# #~ logging.debug(player.groupdict())
# (pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
# if oldcards:
# oldcards = [c.strip() for c in oldcards.split(' ')]
# if newcards:
# newcards = [c.strip() for c in newcards.split(' ')]
# if street=='ANTES':
# return
# elif street=='THIRD':
# # we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# # hero: [xx][o]
# # others: [o]
# hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
# elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# # 4th:
# # hero: [xxo] [o]
# # others: [o] [o]
# # 5th:
# # hero: [xxoo] [o]
# # others: [oo] [o]
# # 6th:
# # hero: [xxooo] [o]
# # others: [ooo] [o]
# hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# # we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
# elif street=='SEVENTH' and newcards:
# # hero: [xxoooo] [x]
# # others: not reported.
# hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
acts = action.groupdict()
if action.group('ATYPE') == ' raises': if action.group('ATYPE') == ' raises':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == ' calls': elif action.group('ATYPE') == ' calls':
@ -294,7 +390,7 @@ follow : whether to tail -f the input"""
elif action.group('ATYPE') == ' checks': elif action.group('ATYPE') == ' checks':
hand.addCheck( street, action.group('PNAME')) hand.addCheck( street, action.group('PNAME'))
elif action.group('ATYPE') == ' discards': elif action.group('ATYPE') == ' discards':
hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED')) hand.addDiscard(street, action.group('PNAME'), action.group('BET'), action.group('DISCARDED'))
elif action.group('ATYPE') == ' stands pat': elif action.group('ATYPE') == ' stands pat':
hand.addStandsPat( street, action.group('PNAME')) hand.addStandsPat( street, action.group('PNAME'))
else: else:
@ -302,9 +398,9 @@ follow : whether to tail -f the input"""
def readShowdownActions(self, hand): def readShowdownActions(self, hand):
# TODO: pick up mucks also??
for shows in self.re_ShowdownAction.finditer(hand.handText): for shows in self.re_ShowdownAction.finditer(hand.handText):
cards = shows.group('CARDS') cards = shows.group('CARDS').split(' ')
cards = set(cards.split(' '))
hand.addShownCards(cards, shows.group('PNAME')) hand.addShownCards(cards, shows.group('PNAME'))
def readCollectPot(self,hand): def readCollectPot(self,hand):
@ -315,12 +411,17 @@ follow : whether to tail -f the input"""
for m in self.re_ShownCards.finditer(hand.handText): for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None: if m.group('CARDS') is not None:
cards = m.group('CARDS') cards = m.group('CARDS')
cards = set(cards.split(' ')) cards = cards.split(' ') # needs to be a list, not a set--stud needs the order
hand.addShownCards(cards=cards, player=m.group('PNAME'))
(shown, mucked) = (False, False)
if m.group('SHOWED') == "showed": shown = True
elif m.group('SHOWED') == "mucked": mucked = True
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked)
if __name__ == "__main__": if __name__ == "__main__":
parser = OptionParser() parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/pokerstars/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt") parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/stars/horse/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-") parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False) parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet", parser.add_option("-q", "--quiet",

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@
######################################################################## ########################################################################
# How to write a new stat: # How to write a new stat:
# 0 Do not use a name like "xyz_2". Names ending in _ and a single digit are
# used to indicate the number of decimal places the user wants to see in the Hud.
# 1 You can see a listing of all the raw stats (e.g., from the HudCache table) # 1 You can see a listing of all the raw stats (e.g., from the HudCache table)
# by running Database.py as a stand along program. You need to combine # by running Database.py as a stand along program. You need to combine
# those raw stats to get stats to present to the HUD. If you need more # those raw stats to get stats to present to the HUD. If you need more
@ -50,16 +52,34 @@
# pyGTK modules # pyGTK modules
import pygtk import pygtk
import gtk import gtk
import re
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
import Database import Database
re_Places = re.compile("_[0-9]$")
re_Percent = re.compile("%$")
def do_tip(widget, tip): def do_tip(widget, tip):
widget.set_tooltip_text(tip) widget.set_tooltip_text(tip)
def do_stat(stat_dict, player = 24, stat = 'vpip'): def do_stat(stat_dict, player = 24, stat = 'vpip'):
return eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player}) match = re_Places.search(stat)
if match == None:
result = eval("%(stat)s(stat_dict, %(player)d)" % {'stat': stat, 'player': player})
else:
base = stat[0:-2]
places = int(stat[-1:])
result = eval("%(stat)s(stat_dict, %(player)d)" % {'stat': base, 'player': player})
match = re_Percent.search(result[1])
if match == None:
result = (result[0], "%.*f" % (places, result[0]), result[2], result[3], result[4], result[5])
else:
result = (result[0], "%.*f%%" % (places, 100*result[0]), result[2], result[3], result[4], result[5])
return result
# OK, for reference the tuple returned by the stat is: # OK, for reference the tuple returned by the stat is:
# 0 - The stat, raw, no formating, eg 0.33333333 # 0 - The stat, raw, no formating, eg 0.33333333
@ -108,26 +128,6 @@ def vpip(stat_dict, player):
'Voluntarily Put In Pot %' 'Voluntarily Put In Pot %'
) )
def vpip_0(stat_dict, player):
""" Voluntarily put $ in the pot (no decimals)."""
stat = 0.0
try:
stat = float(stat_dict[player]['vpip'])/float(stat_dict[player]['n'])
return (stat,
'%2.0f' % (100*stat) + '%',
'v=%2.0f' % (100*stat) + '%',
'vpip=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']),
'vpip'
)
except: return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wtsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'wtsd'
)
def pfr(stat_dict, player): def pfr(stat_dict, player):
""" Preflop (3rd street) raise.""" """ Preflop (3rd street) raise."""
stat = 0.0 stat = 0.0
@ -149,27 +149,6 @@ def pfr(stat_dict, player):
'Pre-Flop Raise %' 'Pre-Flop Raise %'
) )
def pfr_0(stat_dict, player):
""" Preflop (3rd street) raise (no decimals)."""
stat = 0.0
try:
stat = float(stat_dict[player]['pfr'])/float(stat_dict[player]['n'])
return (stat,
'%2.0f' % (100*stat) + '%',
'p=%2.0f' % (100*stat) + '%',
'pfr=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']),
'pfr'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'p=%2.0f' % (0) + '%',
'pfr=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'pfr'
)
def wtsd(stat_dict, player): def wtsd(stat_dict, player):
""" Went to SD when saw flop/4th.""" """ Went to SD when saw flop/4th."""
stat = 0.0 stat = 0.0
@ -191,27 +170,6 @@ def wtsd(stat_dict, player):
'% went to showdown' '% went to showdown'
) )
def wtsd_0(stat_dict, player):
""" Went to SD when saw flop/4th."""
stat = 0.0
try:
stat = float(stat_dict[player]['sd'])/float(stat_dict[player]['saw_f'])
return (stat,
'%2.0f' % (100*stat) + '%',
'w=%2.0f' % (100*stat) + '%',
'wtsd=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['sd'], stat_dict[player]['saw_f']),
'% went to showdown'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wtsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% went to showdown'
)
def wmsd(stat_dict, player): def wmsd(stat_dict, player):
""" Won $ at showdown.""" """ Won $ at showdown."""
stat = 0.0 stat = 0.0
@ -233,28 +191,7 @@ def wmsd(stat_dict, player):
'% won money at showdown' '% won money at showdown'
) )
def wmsd_0(stat_dict, player): def profit100(stat_dict, player):
""" Won $ at showdown."""
stat = 0.0
try:
stat = float(stat_dict[player]['wmsd'])/float(stat_dict[player]['sd'])
return (stat,
'%2.0f' % (100*stat) + '%',
'w=%2.0f' % (100*stat) + '%',
'wmsd=%2.0f' % (100*stat) + '%',
'(%5.1f/%d)' % (float(stat_dict[player]['wmsd']), stat_dict[player]['sd']),
'% won money at showdown'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'w=%2.0f' % (0) + '%',
'wmsd=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% won money at showdown'
)
def profit100_0(stat_dict, player):
""" Profit won per 100 hands (no decimal places).""" """ Profit won per 100 hands (no decimal places)."""
stat = 0.0 stat = 0.0
try: try:
@ -356,27 +293,6 @@ def steal(stat_dict, player):
except: except:
return (stat, 'NA', 'st=NA', 'steal=NA', '(0/0)', '% steal attempted') return (stat, 'NA', 'st=NA', 'steal=NA', '(0/0)', '% steal attempted')
def steal_0(stat_dict, player):
""" Steal %."""
stat = 0.0
try:
stat = float(stat_dict[player]['steal'])/float(stat_dict[player]['steal_opp'])
return (stat,
'%2.0f' % (100*stat) + '%',
'st=%2.0f' % (100*stat) + '%',
'steal=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['steal'], stat_dict[player]['steal_opp']),
'% steal attempted'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'st=%2.0f' % (0) + '%',
'steal=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'% steal attempted'
)
def f_SB_steal(stat_dict, player): def f_SB_steal(stat_dict, player):
""" Folded SB to steal.""" """ Folded SB to steal."""
stat = 0.0 stat = 0.0
@ -417,27 +333,7 @@ def f_BB_steal(stat_dict, player):
'(0/0)', '(0/0)',
'% folded BB to steal') '% folded BB to steal')
def f_BB_steal_0(stat_dict, player): def three_B(stat_dict, player):
""" Folded BB to steal."""
stat = 0.0
try:
stat = float(stat_dict[player]['bbnotdef'])/float(stat_dict[player]['bbstolen'])
return (stat,
'%2.0f' % (100*stat) + '%',
'fBB=%2.0f' % (100*stat) + '%',
'fBB_s=%2.0f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['bbnotdef'], stat_dict[player]['bbstolen']),
'% folded BB to steal'
)
except:
return (stat,
'NA',
'fBB=NA',
'fBB_s=NA',
'(0/0)',
'% folded BB to steal')
def three_B_0(stat_dict, player):
""" Three bet preflop/3rd.""" """ Three bet preflop/3rd."""
stat = 0.0 stat = 0.0
try: try:
@ -479,7 +375,7 @@ def WMsF(stat_dict, player):
'% won$/saw flop/4th' '% won$/saw flop/4th'
) )
def a_freq_1(stat_dict, player): def a_freq1(stat_dict, player):
""" Flop/4th aggression frequency.""" """ Flop/4th aggression frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -500,7 +396,7 @@ def a_freq_1(stat_dict, player):
'Aggression Freq flop/4th' 'Aggression Freq flop/4th'
) )
def a_freq_2(stat_dict, player): def a_freq2(stat_dict, player):
""" Turn/5th aggression frequency.""" """ Turn/5th aggression frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -521,7 +417,7 @@ def a_freq_2(stat_dict, player):
'Aggression Freq turn/5th' 'Aggression Freq turn/5th'
) )
def a_freq_3(stat_dict, player): def a_freq3(stat_dict, player):
""" River/6th aggression frequency.""" """ River/6th aggression frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -542,7 +438,7 @@ def a_freq_3(stat_dict, player):
'Aggression Freq river/6th' 'Aggression Freq river/6th'
) )
def a_freq_4(stat_dict, player): def a_freq4(stat_dict, player):
""" 7th street aggression frequency.""" """ 7th street aggression frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -591,34 +487,7 @@ def a_freq_123(stat_dict, player):
'Post-Flop Aggression Freq' 'Post-Flop Aggression Freq'
) )
def a_freq_123_0(stat_dict, player): def cb1(stat_dict, player):
""" Post-Flop aggression frequency (no decimals)."""
stat = 0.0
try:
stat = float( stat_dict[player]['aggr_1'] + stat_dict[player]['aggr_2'] + stat_dict[player]['aggr_3']) / float( stat_dict[player]['saw_1'] + stat_dict[player]['saw_2'] + stat_dict[player]['saw_3']);
return (stat,
'%2.0f' % (100*stat) + '%',
'afq=%2.0f' % (100*stat) + '%',
'postf_aggfq=%2.0f' % (100*stat) + '%',
'(%d/%d)' % ( stat_dict[player]['aggr_1']
+ stat_dict[player]['aggr_2']
+ stat_dict[player]['aggr_3']
, stat_dict[player]['saw_1']
+ stat_dict[player]['saw_2']
+ stat_dict[player]['saw_3']
),
'Post-Flop Aggression Freq'
)
except:
return (stat,
'%2.0f' % (0) + '%',
'a3=%2.0f' % (0) + '%',
'a_fq_3=%2.0f' % (0) + '%',
'(%d/%d)' % (0, 0),
'Post-Flop Aggression Freq'
)
def cb_1(stat_dict, player):
""" Flop continuation bet.""" """ Flop continuation bet."""
stat = 0.0 stat = 0.0
try: try:
@ -639,7 +508,7 @@ def cb_1(stat_dict, player):
'% continuation bet flop/4th' '% continuation bet flop/4th'
) )
def cb_2(stat_dict, player): def cb2(stat_dict, player):
""" Turn continuation bet.""" """ Turn continuation bet."""
stat = 0.0 stat = 0.0
try: try:
@ -660,7 +529,7 @@ def cb_2(stat_dict, player):
'% continuation bet turn/5th' '% continuation bet turn/5th'
) )
def cb_3(stat_dict, player): def cb3(stat_dict, player):
""" River continuation bet.""" """ River continuation bet."""
stat = 0.0 stat = 0.0
try: try:
@ -681,7 +550,7 @@ def cb_3(stat_dict, player):
'% continuation bet river/6th' '% continuation bet river/6th'
) )
def cb_4(stat_dict, player): def cb4(stat_dict, player):
""" 7th street continuation bet.""" """ 7th street continuation bet."""
stat = 0.0 stat = 0.0
try: try:
@ -702,7 +571,7 @@ def cb_4(stat_dict, player):
'% continuation bet 7th' '% continuation bet 7th'
) )
def ffreq_1(stat_dict, player): def ffreq1(stat_dict, player):
""" Flop/4th fold frequency.""" """ Flop/4th fold frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -723,7 +592,7 @@ def ffreq_1(stat_dict, player):
'% fold frequency flop/4th' '% fold frequency flop/4th'
) )
def ffreq_2(stat_dict, player): def ffreq2(stat_dict, player):
""" Turn/5th fold frequency.""" """ Turn/5th fold frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -744,7 +613,7 @@ def ffreq_2(stat_dict, player):
'% fold frequency turn/5th' '% fold frequency turn/5th'
) )
def ffreq_3(stat_dict, player): def ffreq3(stat_dict, player):
""" River/6th fold frequency.""" """ River/6th fold frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -765,7 +634,7 @@ def ffreq_3(stat_dict, player):
'% fold frequency river/6th' '% fold frequency river/6th'
) )
def ffreq_4(stat_dict, player): def ffreq4(stat_dict, player):
""" 7th fold frequency.""" """ 7th fold frequency."""
stat = 0.0 stat = 0.0
try: try:
@ -804,24 +673,24 @@ if __name__== "__main__":
print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f') print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd') print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal') print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_sb_steal') print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_bb_steal') print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_b_0') print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsf') print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_1') print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_2') print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_3') print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_4') print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123') print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123_0') print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123_0')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_1') print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_2') print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_3') print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb_4') print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb4')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_1') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq1')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_2') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq2')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_3') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq3')
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq_4') print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq4')
print "\n" print "\n"
print "\n\nLegal stats:" print "\n\nLegal stats:"

View File

@ -231,7 +231,12 @@ def discover_nt_by_name(c, tablename):
titles = {} titles = {}
win32gui.EnumWindows(win_enum_handler, titles) win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles: for hwnd in titles:
#print "Tables.py: tablename =", tablename, "title =", titles[hwnd]
try:
# this can blow up in XP on some windows, eg firefox displaying http://docs.python.org/tutorial/classes.html
if not tablename in titles[hwnd]: continue if not tablename in titles[hwnd]: continue
except:
continue
if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window if 'History for table:' in titles[hwnd]: continue # Everleaf Network HH viewer window
if 'HUD:' in titles[hwnd]: continue # FPDB HUD 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 if 'Chat:' in titles[hwnd]: continue # Some sites (FTP? PS? Others?) have seperable or seperately constructed chat windows
@ -364,7 +369,7 @@ def clean_title(name):
for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)', for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)', ' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
' \(edu\)', ' \(edu, 6 max\)', ' \(6\)', ' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
' \(speed\)', ' \(speed\)', 'special', 'newVPP',
' no all-in', ' fast', ',', ' 50BB min', '50bb min', '\s+$']: ' no all-in', ' fast', ',', ' 50BB min', '50bb min', '\s+$']:
name = re.sub(pattern, '', name) name = re.sub(pattern, '', name)
name = name.rstrip() name = name.rstrip()

View File

@ -33,12 +33,12 @@ class UltimateBet(HandHistoryConverter):
re_Board = re.compile(r"\[(?P<CARDS>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
# self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)') # self.re_setHandInfoRegex('.*#(?P<HID>[0-9]+): Table (?P<TABLE>[ a-zA-Z]+) - \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) - (?P<GAMETYPE>.*) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+) ET - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+)Table (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True): def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
"""\ """\
in_path (default '-' = sys.stdin) in_path (default '-' = sys.stdin)
out_path (default '-' = sys.stdout) out_path (default '-' = sys.stdout)
follow : whether to tail -f the input""" follow : whether to tail -f the input"""
HandHistoryConverter.__init__(self, in_path, out_path, sitename="UltimateBet", follow=follow) HandHistoryConverter.__init__(self, in_path, out_path, sitename="UltimateBet", follow=follow, index=index)
logging.info("Initialising UltimateBetconverter class") logging.info("Initialising UltimateBetconverter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"

381
pyfpdb/Win2dayToFpdb.py Executable file
View File

@ -0,0 +1,381 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2008, Carl Gherardi
#
# 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
########################################################################
import sys
import datetime
from HandHistoryConverter import *
# Win2day HH Format
class Win2day(HandHistoryConverter):
# Static regexes
#<HISTORY ID="102271403" SESSION="session31237702.xml" TABLE="Innsbruck 3" GAME="GAME_THM" GAMETYPE="GAMETYPE_REAL" GAMEKIND="GAMEKIND_CASH" TABLECURRENCY="EUR" LIMIT="NL" STAKES="0.25/0.50" DATE="1246909773" WIN="0.00" LOSS="0.50">
#'^<HISTORY ID="(?P<HID>[0-9]+)" SESSION="session[0-9]+\.xml" TABLE="(?P<TABLE>[- a-zA-Z0-9]+)" GAME="(?P<GAME>[_A-Z]+)" GAMETYPE="[_a-zA-Z]+" GAMEKIND="[_a-zA-Z]+" TABLECURRENCY="(?P<CURRENCY>[A-Z]+)" LIMIT="(?P<LIMIT>NL|PL)" STAKES="(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)" DATE="(?P<DATETIME>[0-9]+)" WIN="[.0-9]+" LOSS="[.0-9]+">$'
re_GameInfo = re.compile('^<HISTORY ID="(?P<HID>[0-9]+)" SESSION="session[0-9]+\.xml" TABLE="(?P<TABLE>[- a-zA-Z0-9]+)" GAME="(?P<GAME>[_A-Z]+)" GAMETYPE="[_a-zA-Z]+" GAMEKIND="[_a-zA-Z]+" TABLECURRENCY="(?P<CURRENCY>[A-Z]+)" LIMIT="(?P<LIMIT>NL|PL)" STAKES="(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)" DATE="(?P<DATETIME>[0-9]+)" WIN="[.0-9]+" LOSS="[.0-9]+">', re.MULTILINE)
re_SplitHands = re.compile('</HISTORY>')
re_HandInfo = re.compile("^Table \'(?P<TABLE>[- a-zA-Z]+)\'(?P<TABLEATTRIBUTES>.+?$)?", re.MULTILINE)
re_Button = re.compile('<ACTION TYPE="HAND_DEAL" PLAYER="(?P<BUTTON>[^"]+)">\n<CARD LINK="[0-9b]+"></CARD>\n<CARD LINK="[0-9b]+"></CARD></ACTION>\n<ACTION TYPE="ACTION_', re.MULTILINE)
#<PLAYER NAME="prato" SEAT="1" AMOUNT="61.29"></PLAYER>
re_PlayerInfo = re.compile('^<PLAYER NAME="(?P<PNAME>.*)" SEAT="(?P<SEAT>[0-9]+)" AMOUNT="(?P<CASH>[.0-9]+)"></PLAYER>', re.MULTILINE)
re_Card = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD>', re.MULTILINE)
re_BoardLast = re.compile('^<CARD LINK="(?P<CARD>[0-9]+)"></CARD></ACTION>', re.MULTILINE)
def __init__(self, in_path = '-', out_path = '-', follow = False, autostart=True, index=0):
HandHistoryConverter.__init__(self, in_path, out_path, sitename="Win2day", follow=follow, index=index)
logging.info("Initialising Win2day converter class")
self.filetype = "text"
self.codepage = "cp1252"
self.sideID = 4
if autostart:
self.start()
def compilePlayerRegexs(self, hand):
players = set([player[1] for player in hand.players])
if not players <= self.compiledPlayers: # x <= y means 'x is subset of y'
# we need to recompile the player regexs.
self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re)
#<ACTION TYPE="HAND_BLINDS" PLAYER="prato" KIND="HAND_SB" VALUE="0.25"></ACTION>
self.re_PostSB = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_SB" VALUE="(?P<SB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_BB" VALUE="(?P<BB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s: posts the ante \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBoth = re.compile(r'^<ACTION TYPE="HAND_BLINDS" PLAYER="%s" KIND="HAND_AB" VALUE="(?P<SBBB>[.0-9]+)"></ACTION>' % player_re, re.MULTILINE)
#r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n<CARD LINK="(?P<CARD1>[0-9]+)"></CARD>\n<CARD LINK="(?P<CARD2>[0-9]+)"></CARD></ACTION>'
self.re_HeroCards = re.compile(r'<ACTION TYPE="HAND_DEAL" PLAYER="%s">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]"></CARD>)</ACTION>' % player_re, re.MULTILINE)
#'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>'
self.re_Action = re.compile(r'^<ACTION TYPE="(?P<ATYPE>[_A-Z]+)" PLAYER="%s"( VALUE="(?P<BET>[.0-9]+)")?></ACTION>' % player_re, re.MULTILINE)
self.re_ShowdownAction = re.compile(r'<RESULT PLAYER="%s" WIN="[.0-9]+" HAND="(?P<HAND>\(\$STR_G_FOLD\)|[\$\(\)_ A-Z]+)">\n(?P<CARDS><CARD LINK="[0-9]+"></CARD>\n<CARD LINK="[0-9]+"></CARD>)</RESULT>' % player_re, re.MULTILINE)
#<RESULT PLAYER="wig0r" WIN="4.10" HAND="$(STR_G_WIN_TWOPAIR) $(STR_G_CARDS_TENS) $(STR_G_ANDTEXT) $(STR_G_CARDS_EIGHTS)">
#
self.re_CollectPot = re.compile(r'<RESULT PLAYER="%s" WIN="(?P<POT>[.\d]+)" HAND=".+">' % player_re, re.MULTILINE)
self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE)
self.re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %s \(.*\) showed \[(?P<CARDS>.*)\].*" % player_re, re.MULTILINE)
def readSupportedGames(self):
return [["ring", "hold", "nl"],
["ring", "hold", "pl"],
["ring", "hold", "fl"],
["ring", "stud", "fl"],
["ring", "draw", "fl"],
["ring", "omaha", "pl"]
]
def determineGameType(self, handText):
info = {'type':'ring'}
m = self.re_GameInfo.search(handText)
if not m:
print "determineGameType:", handText
return None
mg = m.groupdict()
# translations from captured groups to our info strings
#limits = { 'NL':'nl', 'PL':'pl', 'Limit':'fl' }
limits = { 'NL':'nl', 'PL':'pl'}
games = { # base, category
"GAME_THM" : ('hold','holdem'),
# 'Omaha' : ('hold','omahahi'),
#'Omaha Hi/Lo' : ('hold','omahahilo'),
# 'Razz' : ('stud','razz'),
#'7 Card Stud' : ('stud','studhi'),
# 'Badugi' : ('draw','badugi')
}
if 'LIMIT' in mg:
info['limitType'] = limits[mg['LIMIT']]
if 'GAME' in mg:
(info['base'], info['category']) = games[mg['GAME']]
if 'SB' in mg:
info['sb'] = mg['SB']
if 'BB' in mg:
info['bb'] = mg['BB']
if 'CURRENCY' in mg:
info['currency'] = mg['CURRENCY']
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
def readHandInfo(self, hand):
info = {}
m = self.re_HandInfo.search(hand.handText,re.DOTALL)
if m:
info.update(m.groupdict())
# TODO: Be less lazy and parse maxseats from the HandInfo regex
if m.group('TABLEATTRIBUTES'):
m2 = re.search("\s*(\d+)-max", m.group('TABLEATTRIBUTES'))
hand.maxseats = int(m2.group(1))
m = self.re_GameInfo.search(hand.handText)
if m: info.update(m.groupdict())
m = self.re_Button.search(hand.handText)
if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info)
for key in info:
if key == 'DATETIME':
# Win2day uses UTC timestamp
hand.starttime = datetime.datetime.fromtimestamp(int(info[key]))
if key == 'HID':
hand.handid = info[key]
if key == 'TABLE':
hand.tablename = info[key]
if key == 'BUTTON':
hand.buttonpos = info[key]
def readButton(self, hand):
m = self.re_Button.search(hand.handText)
if m:
for player in hand.players:
if player[1] == m.group('BUTTON'):
hand.buttonpos = player[0]
break
else:
logging.info('readButton: not found')
def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
# This re fails if, say, river is missing; then we don't get the ** that starts the river.
if hand.gametype['base'] in ("hold"):
#m = re.search(r"\*\*\* HOLE CARDS \*\*\*(?P<PREFLOP>.+(?=\*\*\* FLOP \*\*\*)|.+)"
# r"(\*\*\* FLOP \*\*\*(?P<FLOP> \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?"
# r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P<TURN>\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?"
# r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P<RIVER>\[\S\S\].+))?", hand.handText,re.DOTALL)
m = re.search('<ACTION TYPE="HAND_BLINDS" PLAYER=".+" KIND="HAND_BB" VALUE="[.0-9]+"></ACTION>(?P<PREFLOP>.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP")|.+)'
'((?P<FLOP><ACTION TYPE="HAND_BOARD" VALUE="BOARD_FLOP" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN")|.+))?'
'((?P<TURN><ACTION TYPE="HAND_BOARD" VALUE="BOARD_TURN" POT="[.0-9]+">.+(?=<ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER")|.+))?'
'((?P<RIVER><ACTION TYPE="HAND_BOARD" VALUE="BOARD_RIVER" POT="[.0-9]+">.+(?=<SHOWDOWN NAME="HAND_SHOWDOWN")|.+))?', hand.handText,re.DOTALL)
hand.addStreets(m)
def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand
if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP)
#print "DEBUG readCommunityCards:", street, hand.streets.group(street)
boardCards = set([])
if street == 'FLOP':
m = self.re_Card.findall(hand.streets[street])
for card in m:
boardCards.add(self.convertWin2dayCards(card))
else:
m = self.re_BoardLast.search(hand.streets[street])
boardCards.add(self.convertWin2dayCards(m.group('CARD')))
hand.setCommunityCards(street, boardCards)
def readAntes(self, hand):
logging.debug("reading antes")
m = self.re_Antes.finditer(hand.handText)
for player in m:
#~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))
hand.addAnte(player.group('PNAME'), player.group('ANTE'))
def readBringIn(self, hand):
m = self.re_BringIn.search(hand.handText,re.DOTALL)
if m:
#~ logging.debug("readBringIn: %s for %s" %(m.group('PNAME'), m.group('BRINGIN')))
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand):
try:
m = self.re_PostSB.search(hand.handText)
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB'))
def readHeroCards(self, hand):
# streets PREFLOP, PREDRAW, and THIRD are special cases beacause
# we need to grab hero's cards
m = self.re_HeroCards.finditer(hand.streets['PREFLOP'])
newcards = []
for found in m:
hand.hero = found.group('PNAME')
for card in self.re_Card.finditer(found.group('CARDS')):
print self.convertWin2dayCards(card.group('CARD'))
newcards.append(self.convertWin2dayCards(card.group('CARD')))
#hand.addHoleCards(holeCards, m.group('PNAME'))
hand.addHoleCards('PREFLOP', hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
def convertWin2dayCards(self, card):
card = int(card)
retCard = ''
cardconvert = { 1:'A',
10:'T',
11:'J',
12:'Q',
13:'K'}
realNumber = card % 13 + 1
if(realNumber in cardconvert):
retCard += cardconvert[realNumber]
else:
retCard += str(realNumber)
if(card > 38):
retCard += 's'
elif(card > 25):
retCard += 'h'
elif(card > 12):
retCard += 'c'
else:
retCard += 'd'
return(retCard)
def readDrawCards(self, hand, street):
logging.debug("readDrawCards")
m = self.re_HeroCards.finditer(hand.streets[street])
if m == None:
hand.involved = False
else:
for player in m:
hand.hero = player.group('PNAME') # Only really need to do this once
newcards = player.group('NEWCARDS')
oldcards = player.group('OLDCARDS')
if newcards == None:
newcards = set()
else:
newcards = set(newcards.split(' '))
if oldcards == None:
oldcards = set()
else:
oldcards = set(oldcards.split(' '))
hand.addDrawHoleCards(newcards, oldcards, player.group('PNAME'), street)
def readStudPlayerCards(self, hand, street):
# See comments of reference implementation in FullTiltToFpdb.py
logging.debug("readStudPlayerCards")
m = self.re_HeroCards.finditer(hand.streets[street])
for player in m:
#~ logging.debug(player.groupdict())
(pname, oldcards, newcards) = (player.group('PNAME'), player.group('OLDCARDS'), player.group('NEWCARDS'))
if oldcards:
oldcards = [c.strip() for c in oldcards.split(' ')]
if newcards:
newcards = [c.strip() for c in newcards.split(' ')]
if street=='ANTES':
return
elif street=='THIRD':
# we'll have observed hero holecards in CARDS and thirdstreet open cards in 'NEWCARDS'
# hero: [xx][o]
# others: [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = oldcards, open = newcards)
elif street in ('FOURTH', 'FIFTH', 'SIXTH'):
# 4th:
# hero: [xxo] [o]
# others: [o] [o]
# 5th:
# hero: [xxoo] [o]
# others: [oo] [o]
# 6th:
# hero: [xxooo] [o]
# others: [ooo] [o]
hand.addPlayerCards(player = player.group('PNAME'), street = street, open = newcards)
# we may additionally want to check the earlier streets tally with what we have but lets trust it for now.
elif street=='SEVENTH' and newcards:
# hero: [xxoooo] [x]
# others: not reported.
hand.addPlayerCards(player = player.group('PNAME'), street = street, closed = newcards)
def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street])
for action in m:
if action.group('ATYPE') == 'ACTION_RAISE':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_CALL':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_ALLIN':
hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_BET':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'ACTION_FOLD':
hand.addFold( street, action.group('PNAME'))
elif action.group('ATYPE') == 'ACTION_CHECK':
hand.addCheck( street, action.group('PNAME'))
elif action.group('ATYPE') == 'ACTION_DISCARD':
hand.addDiscard(street, action.group('PNAME'), action.group('NODISCARDED'), action.group('DISCARDED'))
elif action.group('ATYPE') == 'ACTION_STAND':
hand.addStandsPat( street, action.group('PNAME'))
else:
print "DEBUG: unimplemented readAction: '%s' '%s'" %(action.group('PNAME'),action.group('ATYPE'),)
def readShowdownActions(self, hand):
for shows in self.re_ShowdownAction.finditer(hand.handText):
showdownCards = set([])
for card in self.re_Card.finditer(shows.group('CARDS')):
#print "DEBUG:", card, card.group('CARD'), self.convertWin2dayCards(card.group('CARD'))
showdownCards.add(self.convertWin2dayCards(card.group('CARD')))
hand.addShownCards(showdownCards, shows.group('PNAME'))
def readCollectPot(self,hand):
for m in self.re_CollectPot.finditer(hand.handText):
potcoll = Decimal(m.group('POT'))
if potcoll > 0:
hand.addCollectPot(player=m.group('PNAME'),pot=potcoll)
def readShownCards(self,hand):
for m in self.re_ShownCards.finditer(hand.handText):
if m.group('CARDS') is not None:
cards = m.group('CARDS')
cards = set(cards.split(' '))
hand.addShownCards(cards=cards, player=m.group('PNAME'))
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-")
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
parser.add_option("-q", "--quiet",
action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
parser.add_option("-v", "--verbose",
action="store_const", const=logging.INFO, dest="verbosity")
parser.add_option("--vv",
action="store_const", const=logging.DEBUG, dest="verbosity")
(options, args) = parser.parse_args()
LOG_FILENAME = './logging.out'
logging.basicConfig(filename=LOG_FILENAME,level=options.verbosity)
e = Win2day(in_path = options.ipath, out_path = options.opath, follow = options.follow)

View File

@ -17,26 +17,26 @@
import os import os
import sys import sys
from optparse import OptionParser import threading
import Options
import string
parser = OptionParser() cl_options = string.join(sys.argv[1:])
parser.add_option("-x", "--errorsToConsole", action="store_true", (options, sys.argv) = Options.fpdb_options()
help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName", dest="dbname", default="fpdb",
help="Overrides the default database name")
(options, sys.argv) = parser.parse_args()
if not options.errorsToConsole: if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('fpdb-error-log.txt', 'w', 0) errorFile = open('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile sys.stderr = errorFile
import logging
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import fpdb_db import interlocks
import fpdb_simple import fpdb_simple
import GuiBulkImport import GuiBulkImport
import GuiPlayerStats import GuiPlayerStats
@ -45,6 +45,8 @@ import GuiTableViewer
import GuiAutoImport import GuiAutoImport
import GuiGraphViewer import GuiGraphViewer
import GuiSessionViewer import GuiSessionViewer
import SQL
import Database
import FpdbSQLQueries import FpdbSQLQueries
import Configuration import Configuration
@ -117,11 +119,13 @@ class fpdb:
def dia_create_del_database(self, widget, data=None): def dia_create_del_database(self, widget, data=None):
print "todo: implement dia_create_del_database" print "todo: implement dia_create_del_database"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_create_del_database #end def dia_create_del_database
def dia_create_del_user(self, widget, data=None): def dia_create_del_user(self, widget, data=None):
print "todo: implement dia_create_del_user" print "todo: implement dia_create_del_user"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_create_del_user #end def dia_create_del_user
def dia_database_stats(self, widget, data=None): def dia_database_stats(self, widget, data=None):
@ -130,7 +134,7 @@ class fpdb:
#end def dia_database_stats #end def dia_database_stats
def dia_database_sessions(self, widget, data=None): def dia_database_sessions(self, widget, data=None):
new_sessions_thread=GuiSessionViewer.GuiSessionViewer(self.config, self.querydict) new_sessions_thread=GuiSessionViewer.GuiSessionViewer(self.config, self.sql)
self.threads.append(new_sessions_thread) self.threads.append(new_sessions_thread)
sessions_tab=new_sessions_thread.get_vbox() sessions_tab=new_sessions_thread.get_vbox()
self.add_and_display_tab(sessions_tab, "Sessions") self.add_and_display_tab(sessions_tab, "Sessions")
@ -138,16 +142,19 @@ class fpdb:
def dia_delete_db_parts(self, widget, data=None): def dia_delete_db_parts(self, widget, data=None):
print "todo: implement dia_delete_db_parts" print "todo: implement dia_delete_db_parts"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_delete_db_parts #end def dia_delete_db_parts
def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None): def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None):
print "todo: implement dia_edit_profile" print "todo: implement dia_edit_profile"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_edit_profile #end def dia_edit_profile
def dia_export_db(self, widget, data=None): def dia_export_db(self, widget, data=None):
print "todo: implement dia_export_db" print "todo: implement dia_export_db"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_export_db #end def dia_export_db
def dia_get_db_root_credentials(self): def dia_get_db_root_credentials(self):
@ -173,6 +180,7 @@ class fpdb:
def dia_import_db(self, widget, data=None): def dia_import_db(self, widget, data=None):
print "todo: implement dia_import_db" print "todo: implement dia_import_db"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_import_db #end def dia_import_db
def dia_licensing(self, widget, data=None): def dia_licensing(self, widget, data=None):
@ -181,19 +189,23 @@ class fpdb:
def dia_load_profile(self, widget, data=None): def dia_load_profile(self, widget, data=None):
"""Dialogue to select a file to load a profile from""" """Dialogue to select a file to load a profile from"""
if self.obtain_global_lock() == 0: # returns 0 if successful if self.obtain_global_lock(): # returns true if successful
try: #try:
chooser = gtk.FileChooserDialog(title="Please select a profile file to load", # chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
action=gtk.FILE_CHOOSER_ACTION_OPEN, # action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) # buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile) # chooser.set_filename(self.profile)
response = chooser.run() # response = chooser.run()
chooser.destroy() # chooser.destroy()
if response == gtk.RESPONSE_OK: # if response == gtk.RESPONSE_OK:
self.load_profile(chooser.get_filename()) # self.load_profile(chooser.get_filename())
elif response == gtk.RESPONSE_CANCEL: # elif response == gtk.RESPONSE_CANCEL:
print 'User cancelled loading profile' # print 'User cancelled loading profile'
#except:
# pass
try:
self.load_profile()
except: except:
pass pass
self.release_global_lock() self.release_global_lock()
@ -201,39 +213,41 @@ class fpdb:
def dia_recreate_tables(self, widget, data=None): def dia_recreate_tables(self, widget, data=None):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables""" """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
if self.obtain_global_lock() in (0,2): # returns 0 if successful, 2 if Hands table does not exist if self.obtain_global_lock(): # returns true if successful
lock_released = False #lock_released = False
try: try:
dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables") buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted." diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database " \
+self.db.fdb.database+" on "+self.db.fdb.host+" they will be deleted."
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
response = dia_confirm.run() response = dia_confirm.run()
dia_confirm.destroy() dia_confirm.destroy()
if response == gtk.RESPONSE_YES: if response == gtk.RESPONSE_YES:
if self.db.backend == self.fdb_lock.MYSQL_INNODB: #if self.db.fdb.backend == self.fdb_lock.fdb.MYSQL_INNODB:
# mysql requires locks on all tables or none - easier to release this lock # mysql requires locks on all tables or none - easier to release this lock
# than lock all the other tables # than lock all the other tables
# ToDo: lock all other tables so that lock doesn't have to be released # ToDo: lock all other tables so that lock doesn't have to be released
self.release_global_lock() # self.release_global_lock()
lock_released = True # lock_released = True
self.db.recreate_tables() self.db.fdb.recreate_tables()
else: #else:
# for other dbs use same connection as holds global lock # for other dbs use same connection as holds global lock
self.fdb_lock.recreate_tables() # self.fdb_lock.fdb.recreate_tables()
elif response == gtk.RESPONSE_NO: elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables' print 'User cancelled recreating tables'
except: except:
pass pass
if not lock_released: #if not lock_released:
self.release_global_lock() self.release_global_lock()
#end def dia_recreate_tables #end def dia_recreate_tables
def dia_regression_test(self, widget, data=None): def dia_regression_test(self, widget, data=None):
print "todo: implement dia_regression_test" print "todo: implement dia_regression_test"
self.obtain_global_lock() self.obtain_global_lock()
self.release_global_lock()
#end def dia_regression_test #end def dia_regression_test
def dia_save_profile(self, widget, data=None): def dia_save_profile(self, widget, data=None):
@ -350,28 +364,27 @@ class fpdb:
def load_profile(self): def load_profile(self):
"""Loads profile from the provided path name.""" """Loads profile from the provided path name."""
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.settings = {} self.settings = {}
self.settings['global_lock'] = self.lock
if (os.sep=="/"): if (os.sep=="/"):
self.settings['os']="linuxmac" self.settings['os']="linuxmac"
else: else:
self.settings['os']="windows" self.settings['os']="windows"
self.settings.update({'cl_options': cl_options})
self.settings.update(self.config.get_db_parameters()) self.settings.update(self.config.get_db_parameters())
self.settings.update(self.config.get_tv_parameters()) self.settings.update(self.config.get_tv_parameters())
self.settings.update(self.config.get_import_parameters()) self.settings.update(self.config.get_import_parameters())
self.settings.update(self.config.get_default_paths()) self.settings.update(self.config.get_default_paths())
if self.db!=None: if self.db != None and self.db.fdb != None:
self.db.disconnect() self.db.disconnect()
self.db = fpdb_db.fpdb_db() self.sql = SQL.Sql(type = self.settings['db-type'], db_server = self.settings['db-server'])
#print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName'] self.db = Database.Database(self.config, sql = self.sql)
self.db.connect(self.settings['db-backend'],
self.settings['db-host'], if self.db.fdb.wrongDbVersion:
self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
if self.db.wrongDbVersion:
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
label = gtk.Label("An invalid DB version or missing tables have been detected.") label = gtk.Label("An invalid DB version or missing tables have been detected.")
@ -389,9 +402,16 @@ class fpdb:
response = diaDbVersionWarning.run() response = diaDbVersionWarning.run()
diaDbVersionWarning.destroy() diaDbVersionWarning.destroy()
if self.status_bar == None:
self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.fdb.database, self.db.fdb.host))
self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show()
else:
self.status_bar.set_text("Status: Connected to %s database named %s on host %s" % (self.db.get_backend_name(),self.db.fdb.database, self.db.fdb.host))
# Database connected to successfully, load queries to pass on to other classes # Database connected to successfully, load queries to pass on to other classes
self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name()) self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name())
self.db.db.rollback() self.db.connection.rollback()
#end def load_profile #end def load_profile
def not_implemented(self, widget, data=None): def not_implemented(self, widget, data=None):
@ -399,17 +419,18 @@ class fpdb:
#end def not_implemented #end def not_implemented
def obtain_global_lock(self): def obtain_global_lock(self):
print "\nTaking global lock ..." ret = self.lock.acquire(False) # will return false if lock is already held
self.fdb_lock = fpdb_db.fpdb_db() if ret:
self.fdb_lock.connect(self.settings['db-backend'], print "\nGlobal lock taken ..."
self.settings['db-host'], else:
self.settings['db-databaseName'], print "\nFailed to get global lock."
self.settings['db-user'], return ret
self.settings['db-password']) # need to release it later:
return self.fdb_lock.get_global_lock() # self.lock.release()
#end def obtain_global_lock #end def obtain_global_lock
def quit(self, widget, data): def quit(self, widget, data=None):
print "Quitting normally" print "Quitting normally"
#check if current settings differ from profile, if so offer to save or abort #check if current settings differ from profile, if so offer to save or abort
self.db.disconnect() self.db.disconnect()
@ -417,9 +438,8 @@ class fpdb:
#end def quit_cliecked #end def quit_cliecked
def release_global_lock(self): def release_global_lock(self):
self.fdb_lock.db.rollback() self.lock.release()
self.fdb_lock.disconnect() print "Global lock released.\n"
print "Global lock released."
#end def release_global_lock #end def release_global_lock
def tab_abbreviations(self, widget, data=None): def tab_abbreviations(self, widget, data=None):
@ -444,13 +464,13 @@ class fpdb:
#end def tab_bulk_import #end def tab_bulk_import
def tab_player_stats(self, widget, data=None): def tab_player_stats(self, widget, data=None):
new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.config, self.querydict, self.window) new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.config, self.sql, self.window)
self.threads.append(new_ps_thread) self.threads.append(new_ps_thread)
ps_tab=new_ps_thread.get_vbox() ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Player Stats") self.add_and_display_tab(ps_tab, "Player Stats")
def tab_positional_stats(self, widget, data=None): def tab_positional_stats(self, widget, data=None):
new_ps_thread=GuiPositionalStats.GuiPositionalStats(self.config, self.querydict) new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql)
self.threads.append(new_ps_thread) self.threads.append(new_ps_thread)
ps_tab=new_ps_thread.get_vbox() ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Positional Stats") self.add_and_display_tab(ps_tab, "Positional Stats")
@ -468,7 +488,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
def tab_table_viewer(self, widget, data=None): def tab_table_viewer(self, widget, data=None):
"""opens a table viewer tab""" """opens a table viewer tab"""
#print "start of tab_table_viewer" #print "start of tab_table_viewer"
new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings) new_tv_thread=GuiTableViewer.GuiTableViewer(self.db.fdb, self.settings)
self.threads.append(new_tv_thread) self.threads.append(new_tv_thread)
tv_tab=new_tv_thread.get_vbox() tv_tab=new_tv_thread.get_vbox()
self.add_and_display_tab(tv_tab, "Table Viewer") self.add_and_display_tab(tv_tab, "Table Viewer")
@ -477,7 +497,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
def tabGraphViewer(self, widget, data=None): def tabGraphViewer(self, widget, data=None):
"""opens a graph viewer tab""" """opens a graph viewer tab"""
#print "start of tabGraphViewer" #print "start of tabGraphViewer"
new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config) new_gv_thread = GuiGraphViewer.GuiGraphViewer(self.sql, self.config)
self.threads.append(new_gv_thread) self.threads.append(new_gv_thread)
gv_tab=new_gv_thread.get_vbox() gv_tab=new_gv_thread.get_vbox()
self.add_and_display_tab(gv_tab, "Graphs") self.add_and_display_tab(gv_tab, "Graphs")
@ -485,9 +505,10 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
def __init__(self): def __init__(self):
self.threads = [] self.threads = []
# no more than 1 process can this lock at a time:
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
self.db = None self.db = None
self.config = Configuration.Config(dbname=options.dbname) self.status_bar = None
self.load_profile()
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", self.delete_event) self.window.connect("delete_event", self.delete_event)
@ -522,11 +543,8 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None) self.tab_main_help(None, None)
self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.database, self.db.host))
self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show()
self.window.show() self.window.show()
self.load_profile()
sys.stderr.write("fpdb starting ...") sys.stderr.write("fpdb starting ...")
#end def __init__ #end def __init__

View File

@ -18,20 +18,21 @@
import os import os
import re import re
import sys import sys
import logging
from time import time, strftime from time import time, strftime
import fpdb_simple import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
class fpdb_db: class fpdb_db:
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
def __init__(self): def __init__(self):
"""Simple constructor, doesnt really do anything""" """Simple constructor, doesnt really do anything"""
self.db = None self.db = None
self.cursor = None self.cursor = None
self.sql = {} self.sql = {}
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# Data Structures for index and foreign key creation # Data Structures for index and foreign key creation
# drop_code is an int with possible values: 0 - don't drop for bulk import # drop_code is an int with possible values: 0 - don't drop for bulk import
@ -72,6 +73,8 @@ class fpdb_db:
, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0} , {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
] ]
, [ # indexes for sqlite (list index 4)
]
] ]
self.foreignKeys = [ self.foreignKeys = [
@ -146,12 +149,12 @@ class fpdb_db:
self.settings = {} self.settings = {}
self.settings['os'] = "linuxmac" if os.name != "nt" else "windows" self.settings['os'] = "linuxmac" if os.name != "nt" else "windows"
self.settings.update(config.get_db_parameters()) db = config.get_db_parameters()
self.connect(self.settings['db-backend'], self.connect(backend=db['db-backend'],
self.settings['db-host'], host=db['db-host'],
self.settings['db-databaseName'], database=db['db-databaseName'],
self.settings['db-user'], user=db['db-user'],
self.settings['db-password']) password=db['db-password'])
#end def do_connect #end def do_connect
def connect(self, backend=None, host=None, database=None, def connect(self, backend=None, host=None, database=None,
@ -164,13 +167,13 @@ class fpdb_db:
self.user=user self.user=user
self.password=password self.password=password
self.database=database self.database=database
if backend==self.MYSQL_INNODB: if backend==fpdb_db.MYSQL_INNODB:
import MySQLdb import MySQLdb
try: try:
self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True) self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True)
except: except:
raise fpdb_simple.FpdbError("MySQL connection failed") raise fpdb_simple.FpdbError("MySQL connection failed")
elif backend==self.PGSQL: elif backend==fpdb_db.PGSQL:
import psycopg2 import psycopg2
import psycopg2.extensions import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
@ -179,19 +182,41 @@ class fpdb_db:
# For local domain-socket connections, only DB name is # For local domain-socket connections, only DB name is
# needed, and everything else is in fact undefined and/or # needed, and everything else is in fact undefined and/or
# flat out wrong # flat out wrong
# sqlcoder: This database only connect failed in my windows setup??
# Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere?
connected = False
if self.host == "localhost" or self.host == "127.0.0.1": if self.host == "localhost" or self.host == "127.0.0.1":
try:
self.db = psycopg2.connect(database = database) self.db = psycopg2.connect(database = database)
else: connected = True
except:
pass
#msg = "PostgreSQL direct connection to database (%s) failed, trying with user ..." % (database,)
#print msg
#raise fpdb_simple.FpdbError(msg)
if not connected:
try:
self.db = psycopg2.connect(host = host, self.db = psycopg2.connect(host = host,
user = user, user = user,
password = password, password = password,
database = database) database = database)
except:
msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user)
print msg
raise fpdb_simple.FpdbError(msg)
elif backend==fpdb_db.SQLITE:
logging.info("Connecting to SQLite:%(database)s" % {'database':database})
import sqlite3
self.db = sqlite3.connect(database,detect_types=sqlite3.PARSE_DECLTYPES)
sqlite3.register_converter("bool", lambda x: bool(int(x)))
sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
else: else:
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) raise fpdb_simple.FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor() self.cursor=self.db.cursor()
self.cursor.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED')
# Set up query dictionary as early in the connection process as we can. # Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
self.cursor.execute(self.sql.query['set tx level'])
self.wrongDbVersion=False self.wrongDbVersion=False
try: try:
self.cursor.execute("SELECT * FROM Settings") self.cursor.execute("SELECT * FROM Settings")
@ -222,13 +247,14 @@ class fpdb_db:
def create_tables(self): def create_tables(self):
#todo: should detect and fail gracefully if tables already exist. #todo: should detect and fail gracefully if tables already exist.
logging.debug(self.sql.query['createSettingsTable'])
self.cursor.execute(self.sql.query['createSettingsTable']) self.cursor.execute(self.sql.query['createSettingsTable'])
logging.debug(self.sql.query['createSitesTable'])
self.cursor.execute(self.sql.query['createSitesTable']) self.cursor.execute(self.sql.query['createSitesTable'])
self.cursor.execute(self.sql.query['createGametypesTable']) self.cursor.execute(self.sql.query['createGametypesTable'])
self.cursor.execute(self.sql.query['createPlayersTable']) self.cursor.execute(self.sql.query['createPlayersTable'])
self.cursor.execute(self.sql.query['createAutoratesTable']) self.cursor.execute(self.sql.query['createAutoratesTable'])
self.cursor.execute(self.sql.query['createHandsTable']) self.cursor.execute(self.sql.query['createHandsTable'])
self.cursor.execute(self.sql.query['createBoardCardsTable'])
self.cursor.execute(self.sql.query['createTourneyTypesTable']) self.cursor.execute(self.sql.query['createTourneyTypesTable'])
self.cursor.execute(self.sql.query['createTourneysTable']) self.cursor.execute(self.sql.query['createTourneysTable'])
self.cursor.execute(self.sql.query['createTourneysPlayersTable']) self.cursor.execute(self.sql.query['createTourneysPlayersTable'])
@ -260,8 +286,10 @@ class fpdb_db:
for table in tables: for table in tables:
self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade') self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
elif(self.get_backend_name() == 'SQLite'): elif(self.get_backend_name() == 'SQLite'):
#todo: sqlite version here self.cursor.execute(self.sql.query['list_tables'])
print "Empty function here" for table in self.cursor.fetchall():
logging.debug(self.sql.query['drop_table'] + table[0])
self.cursor.execute(self.sql.query['drop_table'] + table[0])
self.db.commit() self.db.commit()
#end def drop_tables #end def drop_tables
@ -292,6 +320,8 @@ class fpdb_db:
return "MySQL InnoDB" return "MySQL InnoDB"
elif self.backend==3: elif self.backend==3:
return "PostgreSQL" return "PostgreSQL"
elif self.backend==4:
return "SQLite"
else: else:
raise fpdb_simple.FpdbError("invalid backend") raise fpdb_simple.FpdbError("invalid backend")
#end def get_backend_name #end def get_backend_name
@ -301,11 +331,15 @@ class fpdb_db:
#end def get_db_info #end def get_db_info
def fillDefaultData(self): def fillDefaultData(self):
self.cursor.execute("INSERT INTO Settings VALUES (118);") self.cursor.execute("INSERT INTO Settings (version) VALUES (118);")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');") self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');") self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');") self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')")
self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Win2day', 'USD')")
self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#self.cursor.execute("""INSERT INTO TourneyTypes
# (siteId,buyin,fee,knockout,rebuyOrAddon) VALUES
# (1,0,0,0,?)""",(False,) )
#end def fillDefaultData #end def fillDefaultData
def recreate_tables(self): def recreate_tables(self):
@ -538,71 +572,99 @@ class fpdb_db:
self.db.set_isolation_level(1) # go back to normal isolation level self.db.set_isolation_level(1) # go back to normal isolation level
#end def dropAllIndexes #end def dropAllIndexes
def analyzeDB(self): def getLastInsertId(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: if self.backend == self.MYSQL_INNODB:
try: ret = self.db.insert_id()
self.cursor.execute( "lock tables Hands write" ) if ret < 1 or ret > 999999999:
except: print "getLastInsertId(): problem fetching insert_id? ret=", ret
# Table 'fpdb.hands' doesn't exist ret = -1
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: elif self.backend == self.PGSQL:
try: # some options:
self.cursor.execute( "lock table Hands in exclusive mode nowait" ) # currval(hands_id_seq) - use name of implicit seq here
#print "... after lock table, status =", self.cursor.statusmessage # lastval() - still needs sequences set up?
except: # insert ... returning is useful syntax (but postgres specific?)
# relation "hands" does not exist # see rules (fancy trigger type things)
if str(sys.exc_value).find('relation "hands" does not exist') >= 0: self.cursor.execute ("SELECT lastval()")
return(2) row = self.cursor.fetchone()
print "Error! failed to obtain global lock. Close all programs accessing " \ if not row:
+ "database (including fpdb) and try again (%s)." \ print "getLastInsertId(%s): problem fetching lastval? row=" % seq, row
% ( str(sys.exc_value).rstrip('\n'), ) ret = -1
return(1) else:
return(0) ret = row[0]
elif self.backend == fpdb_db.SQLITE:
# don't know how to do this in sqlite
print "getLastInsertId(): not coded for sqlite yet"
ret = -1
else:
print "getLastInsertId(): unknown backend ", self.backend
ret = -1
return ret
def storeHand(self, p): def storeHand(self, p):
#stores into table hands: #stores into table hands:
self.cursor.execute ("""INSERT INTO Hands self.cursor.execute ("""INSERT INTO Hands (
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats tablename,
,playersVpi, playersAtStreet1, playersAtStreet2 sitehandno,
,playersAtStreet3, playersAtStreet4, playersAtShowdown gametypeid,
,street0Raises, street1Raises, street2Raises handstart,
,street3Raises, street4Raises, street1Pot importtime,
,street2Pot, street3Pot, street4Pot seats,
,showdownPot maxseats,
boardcard1,
boardcard2,
boardcard3,
boardcard4,
boardcard5,
-- texture,
playersVpi,
playersAtStreet1,
playersAtStreet2,
playersAtStreet3,
playersAtStreet4,
playersAtShowdown,
street0Raises,
street1Raises,
street2Raises,
street3Raises,
street4Raises,
-- street1Pot,
-- street2Pot,
-- street3Pot,
-- street4Pot,
-- showdownPot
) )
VALUES VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %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'] %s, %s, %s, %s, %s, %s, %s)""",
,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2'] (
,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown'] p['tablename'],
,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises'] p['sitehandno'],
,hudCache['street3Raises'], hudCache['street4Raises'], hudCache['street1Pot'] p['gametypeid'],
,hudCache['street2Pot'], hudCache['street3Pot'], hudCache['street4Pot'] p['handStart'],
,hudCache['showdownPot'] datetime.datetime.today(),
len(p['names']),
p['maxSeats'],
p['boardcard1'],
p['boardcard2'],
p['boardcard3'],
p['boardcard4'],
p['boardcard5'],
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) #return getLastInsertId(backend, conn, cursor)

View File

@ -22,6 +22,7 @@
import os # todo: remove this once import_dir is in fpdb_import import os # todo: remove this once import_dir is in fpdb_import
import sys import sys
from time import time, strftime from time import time, strftime
import logging
import traceback import traceback
import math import math
import datetime import datetime
@ -31,6 +32,7 @@ import re
import fpdb_simple import fpdb_simple
import fpdb_db import fpdb_db
import Database
import fpdb_parse_logic import fpdb_parse_logic
import Configuration import Configuration
@ -57,7 +59,8 @@ class Importer:
self.settings = settings self.settings = settings
self.caller = caller self.caller = caller
self.config = config self.config = config
self.fdb = None self.database = None # database will be the main db interface eventually
self.fdb = None # fdb may disappear or just hold the simple db connection
self.cursor = None self.cursor = None
self.filelist = {} self.filelist = {}
self.dirlist = {} self.dirlist = {}
@ -75,9 +78,13 @@ class Importer:
self.settings.setdefault("minPrint", 30) self.settings.setdefault("minPrint", 30)
self.settings.setdefault("handCount", 0) self.settings.setdefault("handCount", 0)
self.database = Database.Database(self.config) # includes .connection and .sql variables
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.do_connect(self.config) self.fdb.do_connect(self.config)
self.fdb.db.rollback() self.fdb.db.rollback() # make sure all locks are released
self.NEWIMPORT = False
self.allow_hudcache_rebuild = True;
#Set functions #Set functions
def setCallHud(self, value): def setCallHud(self, value):
@ -162,12 +169,19 @@ class Importer:
def runImport(self): def runImport(self):
""""Run full import on self.filelist.""" """"Run full import on self.filelist."""
start = datetime.datetime.now() start = datetime.datetime.now()
print "started at", start, "--", len(self.filelist), "files to import.", self.settings['dropIndexes'] print "Started at", start, "--", len(self.filelist), "files to import.", self.settings['dropIndexes']
if self.settings['dropIndexes'] == 'auto': if self.settings['dropIndexes'] == 'auto':
self.settings['dropIndexes'] = self.calculate_auto() self.settings['dropIndexes'] = self.calculate_auto2(12.0, 500.0)
if self.allow_hudcache_rebuild:
self.settings['dropHudCache'] = self.calculate_auto2(25.0, 500.0) # returns "drop"/"don't drop"
if self.settings['dropIndexes'] == 'drop': if self.settings['dropIndexes'] == 'drop':
self.fdb.prepareBulkImport() self.fdb.prepareBulkImport()
else:
print "No need drop indexes."
#print "dropInd =", self.settings['dropIndexes'], " dropHudCache =", self.settings['dropHudCache']
totstored = 0 totstored = 0
totdups = 0 totdups = 0
totpartial = 0 totpartial = 0
@ -183,7 +197,13 @@ class Importer:
tottime += ttime tottime += ttime
if self.settings['dropIndexes'] == 'drop': if self.settings['dropIndexes'] == 'drop':
self.fdb.afterBulkImport() self.fdb.afterBulkImport()
self.fdb.analyzeDB() else:
print "No need rebuild indexes."
if self.settings['dropHudCache'] == 'drop':
self.database.rebuild_hudcache()
else:
print "No need to rebuild hudcache."
self.database.analyzeDB()
return (totstored, totdups, totpartial, toterrors, tottime) return (totstored, totdups, totpartial, toterrors, tottime)
# else: import threaded # else: import threaded
@ -202,6 +222,41 @@ class Importer:
if self.settings['handsInDB'] > 50000: return "don't drop" if self.settings['handsInDB'] > 50000: return "don't drop"
return "drop" return "drop"
def calculate_auto2(self, scale, increment):
"""A second heuristic to determine a reasonable value of drop/don't drop
This one adds up size of files to import to guess number of hands in them
Example values of scale and increment params might be 10 and 500 meaning
roughly: drop if importing more than 10% (100/scale) of hands in db or if
less than 500 hands in db"""
size_per_hand = 1300.0 # wag based on a PS 6-up FLHE file. Actual value not hugely important
# as values of scale and increment compensate for it anyway.
# decimal used to force float arithmetic
# get number of hands in db
if 'handsInDB' not in self.settings:
try:
tmpcursor = self.fdb.db.cursor()
tmpcursor.execute("Select count(1) from Hands;")
self.settings['handsInDB'] = tmpcursor.fetchone()[0]
except:
pass # if this fails we're probably doomed anyway
# add up size of import files
total_size = 0.0
for file in self.filelist:
if os.path.exists(file):
stat_info = os.stat(file)
total_size += stat_info.st_size
# if hands_in_db is zero or very low, we want to drop indexes, otherwise compare
# import size with db size somehow:
ret = "don't drop"
if self.settings['handsInDB'] < scale * (total_size/size_per_hand) + increment:
ret = "drop"
#print "auto2: handsindb =", self.settings['handsInDB'], "total_size =", total_size, "size_per_hand =", \
# size_per_hand, "inc =", increment, "return:", ret
return ret
#Run import on updated files, then store latest update time. #Run import on updated files, then store latest update time.
def runUpdated(self): def runUpdated(self):
#Check for new files in monitored directories #Check for new files in monitored directories
@ -253,9 +308,7 @@ class Importer:
if os.path.isdir(file): if os.path.isdir(file):
self.addToDirList[file] = [site] + [filter] self.addToDirList[file] = [site] + [filter]
return return
if filter == "passthrough" or filter == "":
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(file, site)
else:
conv = None conv = None
# Load filter, process file, pass returned filename to import_fpdb_file # Load filter, process file, pass returned filename to import_fpdb_file
@ -270,12 +323,38 @@ class Importer:
filter_name = filter.replace("ToFpdb", "") filter_name = filter.replace("ToFpdb", "")
# Example code for using threads & queues: (maybe for obj and import_fpdb_file??)
#def worker():
# while True:
# item = q.get()
# do_work(item)
# q.task_done()
#
#q = Queue()
#for i in range(num_worker_threads):
# t = Thread(target=worker)
# t.setDaemon(True)
# t.start()
#
#for item in source():
# q.put(item)
#
#q.join() # block until all tasks are done
mod = __import__(filter) mod = __import__(filter)
obj = getattr(mod, filter_name, None) obj = getattr(mod, filter_name, None)
if callable(obj): if callable(obj):
conv = obj(in_path = file, out_path = out_path) conv = obj(in_path = file, out_path = out_path, index = 0) # Index into file 0 until changeover
if(conv.getStatus()): if(conv.getStatus() and self.NEWIMPORT == False):
(stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(out_path, site) (stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(out_path, site)
elif (conv.getStatus() and self.NEWIMPORT == True):
#This code doesn't do anything yet
handlist = hhc.getProcessedHands()
self.pos_in_file[file] = hhc.getLastCharacterRead()
for hand in handlist:
hand.prepInsert()
hand.insert()
else: else:
# conversion didn't work # conversion didn't work
# TODO: appropriate response? # TODO: appropriate response?
@ -359,8 +438,9 @@ class Importer:
self.hand=hand self.hand=hand
try: try:
handsId = fpdb_parse_logic.mainParser(self.settings['db-backend'], self.fdb.db handsId = fpdb_parse_logic.mainParser( self.settings, self.fdb
,self.fdb.cursor, self.siteIds[site], category, hand, self.config) , self.siteIds[site], category, hand
, self.config, self.database )
self.fdb.db.commit() self.fdb.db.commit()
stored += 1 stored += 1

View File

@ -18,10 +18,16 @@
#methods that are specific to holdem but not trivial #methods that are specific to holdem but not trivial
import fpdb_simple import fpdb_simple
import fpdb_save_to_db import Database
#parses a holdem hand #parses a holdem hand
def mainParser(backend, db, cursor, siteID, category, hand, config): def mainParser(settings, fdb, siteID, category, hand, config, db = None):
backend = settings['db-backend']
if db == None:
#This is redundant - hopefully fdb will be a Database object in an iteration soon
db = Database.Database(c = config, sql = None)
else:
db = db
category = fpdb_simple.recogniseCategory(hand[0]) category = fpdb_simple.recogniseCategory(hand[0])
base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud" base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud"
@ -34,6 +40,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
#part 1: read hand no and check for duplicate #part 1: read hand no and check for duplicate
siteHandNo = fpdb_simple.parseSiteHandNo(hand[0]) siteHandNo = fpdb_simple.parseSiteHandNo(hand[0])
#print "siteHandNo =", siteHandNo
handStartTime = fpdb_simple.parseHandStartTime(hand[0]) handStartTime = fpdb_simple.parseHandStartTime(hand[0])
isTourney = fpdb_simple.isTourney(hand[0]) isTourney = fpdb_simple.isTourney(hand[0])
@ -45,7 +52,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
break break
#print "small blind line:",smallBlindLine #print "small blind line:",smallBlindLine
gametypeID = fpdb_simple.recogniseGametypeID(backend, db, cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney) gametypeID = fpdb_simple.recogniseGametypeID(backend, fdb.db, fdb.cursor, hand[0], hand[smallBlindLine], siteID, category, isTourney)
if isTourney: if isTourney:
siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0]) siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0])
buyin = fpdb_simple.parseBuyin(hand[0]) buyin = fpdb_simple.parseBuyin(hand[0])
@ -56,9 +63,9 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
tourneyStartTime= handStartTime #todo: read tourney start time tourneyStartTime= handStartTime #todo: read tourney start time
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0]) rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(cursor, siteID, buyin, fee, knockout, rebuyOrAddon) tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(fdb.cursor, siteID, buyin, fee, knockout, rebuyOrAddon)
fpdb_simple.isAlreadyInDB(cursor, gametypeID, siteHandNo) fpdb_simple.isAlreadyInDB(fdb.cursor, gametypeID, siteHandNo)
hand = fpdb_simple.filterCrap(hand, isTourney) hand = fpdb_simple.filterCrap(hand, isTourney)
@ -72,7 +79,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
seatLines.append(line) seatLines.append(line)
names = fpdb_simple.parseNames(seatLines) names = fpdb_simple.parseNames(seatLines)
playerIDs = fpdb_simple.recognisePlayerIDs(cursor, names, siteID) playerIDs = fpdb_simple.recognisePlayerIDs(fdb.cursor, names, siteID)
tmp = fpdb_simple.parseCashesAndSeatNos(seatLines) tmp = fpdb_simple.parseCashesAndSeatNos(seatLines)
startCashes = tmp['startCashes'] startCashes = tmp['startCashes']
seatNos = tmp['seatNos'] seatNos = tmp['seatNos']
@ -111,7 +118,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
tableName = tableResult['tableName'] tableName = tableResult['tableName']
#print "before part5, antes:", antes #print "before part5, antes:", antes
#part 5: final preparations, then call fpdb_save_to_db.* with #part 5: final preparations, then call Database.* with
# the arrays as they are - that file will fill them. # the arrays as they are - that file will fill them.
fpdb_simple.convertCardValues(cardValues) fpdb_simple.convertCardValues(cardValues)
if base == "hold": if base == "hold":
@ -119,13 +126,14 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
fpdb_simple.convertBlindBet(actionTypes, actionAmounts) fpdb_simple.convertBlindBet(actionTypes, actionAmounts)
fpdb_simple.checkPositions(positions) fpdb_simple.checkPositions(positions)
cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, )) fdb.cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, ))
limit_type = cursor.fetchone()[0] limit_type = fdb.cursor.fetchone()[0]
fpdb_simple.convert3B4B(category, limit_type, actionTypes, actionAmounts) fpdb_simple.convert3B4B(category, limit_type, actionTypes, actionAmounts)
totalWinnings = sum(winnings) totalWinnings = sum(winnings)
# if hold'em, use positions and not antes, if stud do not use positions, use antes # if hold'em, use positions and not antes, if stud do not use positions, use antes
# this is used for handsplayers inserts, so still needed even if hudcache update is being skipped
if base == "hold": if base == "hold":
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes
, allIns, actionTypeByNo, winnings, totalWinnings, positions , allIns, actionTypeByNo, winnings, totalWinnings, positions
@ -140,8 +148,8 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
payin_amounts = fpdb_simple.calcPayin(len(names), buyin, fee) payin_amounts = fpdb_simple.calcPayin(len(names), buyin, fee)
if base == "hold": if base == "hold":
result = fpdb_save_to_db.tourney_holdem_omaha( result = db.tourney_holdem_omaha(
config, backend, db, cursor, base, category, siteTourneyNo, buyin config, settings, fdb.db, fdb.cursor, base, category, siteTourneyNo, buyin
, fee, knockout, entries, prizepool, tourneyStartTime , fee, knockout, entries, prizepool, tourneyStartTime
, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo , payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo
, gametypeID, handStartTime, names, playerIDs, startCashes , gametypeID, handStartTime, names, playerIDs, startCashes
@ -149,8 +157,8 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
, winnings, rakes, actionTypes, allIns, actionAmounts , winnings, rakes, actionTypes, allIns, actionAmounts
, actionNos, hudImportData, maxSeats, tableName, seatNos) , actionNos, hudImportData, maxSeats, tableName, seatNos)
elif base == "stud": elif base == "stud":
result = fpdb_save_to_db.tourney_stud( result = db.tourney_stud(
config, backend, db, cursor, base, category, siteTourneyNo config, settings, fdb.db, fdb.cursor, base, category, siteTourneyNo
, buyin, fee, knockout, entries, prizepool, tourneyStartTime , buyin, fee, knockout, entries, prizepool, tourneyStartTime
, payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo , payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo
, gametypeID, handStartTime, names, playerIDs, startCashes , gametypeID, handStartTime, names, playerIDs, startCashes
@ -161,23 +169,23 @@ def mainParser(backend, db, cursor, siteID, category, hand, config):
raise fpdb_simple.FpdbError("unrecognised category") raise fpdb_simple.FpdbError("unrecognised category")
else: else:
if base == "hold": if base == "hold":
result = fpdb_save_to_db.ring_holdem_omaha( result = db.ring_holdem_omaha(
config, backend, db, cursor, base, category, siteHandNo config, settings, fdb.db, fdb.cursor, base, category, siteHandNo
, gametypeID, handStartTime, names, playerIDs , gametypeID, handStartTime, names, playerIDs
, startCashes, positions, cardValues, cardSuits , startCashes, positions, cardValues, cardSuits
, boardValues, boardSuits, winnings, rakes , boardValues, boardSuits, winnings, rakes
, actionTypes, allIns, actionAmounts, actionNos , actionTypes, allIns, actionAmounts, actionNos
, hudImportData, maxSeats, tableName, seatNos) , hudImportData, maxSeats, tableName, seatNos)
elif base == "stud": elif base == "stud":
result = fpdb_save_to_db.ring_stud( result = db.ring_stud(
config, backend, db, cursor, base, category, siteHandNo, gametypeID config, settings, fdb.db, fdb.cursor, base, category, siteHandNo, gametypeID
, handStartTime, names, playerIDs, startCashes, antes , handStartTime, names, playerIDs, startCashes, antes
, cardValues, cardSuits, winnings, rakes, actionTypes, allIns , cardValues, cardSuits, winnings, rakes, actionTypes, allIns
, actionAmounts, actionNos, hudImportData, maxSeats, tableName , actionAmounts, actionNos, hudImportData, maxSeats, tableName
, seatNos) , seatNos)
else: else:
raise fpdb_simple.FpdbError ("unrecognised category") raise fpdb_simple.FpdbError ("unrecognised category")
db.commit() fdb.db.commit()
return result return result
#end def mainParser #end def mainParser

View File

@ -1,173 +0,0 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#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 Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
#This file contains methods to store hands into the db. decides to move this
#into a seperate file since its ugly, fairly long and just generally in the way.
from time import time
import fpdb_simple
MYSQL_INNODB = 2
PGSQL = 3
SQLITE = 4
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
#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
,action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName
,seatNos):
import_options = config.get_import_parameters()
saveActions = False if import_options['saveActions'] == False else True
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudImportData)
#print "before calling store_hands_players_stud, antes:", antes
hands_players_ids = fpdb_simple.store_hands_players_stud(backend, db, cursor, hands_id, player_ids
,start_cashes, antes, card_values
,card_suits, winnings, rakes, seatNos)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types
,allIns, action_amounts, actionNos)
return hands_id
#end def ring_stud
def ring_holdem_omaha(config, backend, db, cursor, base, category, site_hand_no, gametype_id
,hand_start_time, names, player_ids, start_cashes, positions, card_values
,card_suits, board_values, board_suits, winnings, rakes, action_types, allIns
,action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a holdem/omaha hand into the database"""
import_options = config.get_import_parameters()
saveActions = False if import_options['saveActions'] == False else True
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
# print "DEBUG: saveActions = '%s' fastStoreHudCache = '%s'"%(saveActions, fastStoreHudCache)
# print "DEBUG: import_options = ", import_options
t0 = time()
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
t1 = time()
fpdb_simple.fill_board_cards(board_values, board_suits)
t2 = time()
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudImportData)
t3 = time()
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha(
backend, db, cursor, category, hands_id, player_ids, start_cashes
, positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData)
t4 = time()
#print "ring holdem, backend=%d" % backend
if fastStoreHudCache:
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
else:
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
t5 = time()
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
t6 = time()
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
t7 = time()
#print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4, t6-t5)
return hands_id
#end def ring_holdem_omaha
def tourney_holdem_omaha(config, backend, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout
,entries, prizepool, tourney_start, payin_amounts, ranks, tourneyTypeId
,siteId #end of tourney specific params
,site_hand_no, gametype_id, hand_start_time, names, player_ids
,start_cashes, positions, card_values, card_suits, board_values
,board_suits, winnings, rakes, action_types, allIns, action_amounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
"""stores a tourney holdem/omaha hand into the database"""
import_options = config.get_import_parameters()
saveActions = True if import_options['saveActions'] == True else False
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits)
fpdb_simple.fill_board_cards(board_values, board_suits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(backend, db, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_holdem_omaha_tourney(
backend, db, cursor, category, hands_id, player_ids, start_cashes, positions
, card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids)
#print "tourney holdem, backend=%d" % backend
if fastStoreHudCache:
fpdb_simple.storeHudCache2(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
else:
fpdb_simple.storeHudCache(backend, cursor, base, category, gametype_id, hand_start_time, player_ids, hudImportData)
fpdb_simple.store_board_cards(cursor, hands_id, board_values, board_suits)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, action_types, allIns, action_amounts, actionNos)
return hands_id
#end def tourney_holdem_omaha
def tourney_stud(config, backend, db, cursor, base, category, siteTourneyNo, buyin, fee, knockout, entries
,prizepool, tourneyStartTime, payin_amounts, ranks, tourneyTypeId, siteId
,siteHandNo, gametypeId, handStartTime, names, playerIds, startCashes, antes
,cardValues, cardSuits, winnings, rakes, actionTypes, allIns, actionAmounts
,actionNos, hudImportData, maxSeats, tableName, seatNos):
#stores a tourney stud/razz hand into the database
import_options = config.get_import_parameters()
saveActions = True if import_options['saveActions'] == True else False
fastStoreHudCache = True if import_options['fastStoreHudCache'] == True else False
fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits)
tourney_id = fpdb_simple.store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime)
tourneys_players_ids = fpdb_simple.store_tourneys_players(cursor, tourney_id, playerIds, payin_amounts, ranks, winnings)
hands_id = fpdb_simple.storeHands(backend, db, cursor, siteHandNo, gametypeId, handStartTime, names, tableName, maxSeats)
hands_players_ids = fpdb_simple.store_hands_players_stud_tourney(backend, db, cursor, hands_id
, playerIds, startCashes, antes, cardValues, cardSuits
, winnings, rakes, seatNos, tourneys_players_ids)
fpdb_simple.storeHudCache(backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData)
if saveActions:
fpdb_simple.storeActions(cursor, hands_players_ids, actionTypes, allIns, actionAmounts, actionNos)
return hands_id
#end def tourney_stud

View File

@ -1136,21 +1136,14 @@ def storeActions(cursor, handsPlayersIds, actionTypes, allIns, actionAmounts, ac
cursor.executemany("INSERT INTO HandsActions (handsPlayerId, street, actionNo, action, allIn, amount) VALUES (%s, %s, %s, %s, %s, %s)", inserts) cursor.executemany("INSERT INTO HandsActions (handsPlayerId, street, actionNo, action, allIn, amount) VALUES (%s, %s, %s, %s, %s, %s)", inserts)
#end def storeActions #end def storeActions
def store_board_cards(cursor, hands_id, board_values, board_suits):
#stores into table board_cards
cursor.execute ("""INSERT INTO BoardCards (handId, card1Value, card1Suit,
card2Value, card2Suit, card3Value, card3Suit, card4Value, card4Suit,
card5Value, card5Suit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(hands_id, board_values[0], board_suits[0], board_values[1], board_suits[1],
board_values[2], board_suits[2], board_values[3], board_suits[3],
board_values[4], board_suits[4]))
#end def store_board_cards
def storeHands(backend, conn, cursor, site_hand_no, gametype_id def storeHands(backend, conn, cursor, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudCache): ,hand_start_time, names, tableName, maxSeats, hudCache,
board_values, board_suits):
cards = [Card.cardFromValueSuit(v,s) for v,s in zip(board_values,board_suits)]
#stores into table hands: #stores into table hands:
cursor.execute ("""INSERT INTO Hands cursor.execute ("""INSERT INTO Hands
(siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats (siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats
,boardcard1,boardcard2,boardcard3,boardcard4,boardcard5
,playersVpi, playersAtStreet1, playersAtStreet2 ,playersVpi, playersAtStreet1, playersAtStreet2
,playersAtStreet3, playersAtStreet4, playersAtShowdown ,playersAtStreet3, playersAtStreet4, playersAtShowdown
,street0Raises, street1Raises, street2Raises ,street0Raises, street1Raises, street2Raises
@ -1159,9 +1152,11 @@ def storeHands(backend, conn, cursor, site_hand_no, gametype_id
,showdownPot ,showdownPot
) )
VALUES VALUES
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""" """
, (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.datetime.today(), maxSeats , (site_hand_no, gametype_id, hand_start_time, len(names), tableName, datetime.datetime.today(), maxSeats
,cards[0], cards[1], cards[2], cards[3], cards[4]
,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2'] ,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2']
,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown'] ,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown']
,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises'] ,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises']
@ -2121,7 +2116,7 @@ def storeHudCache(backend, cursor, base, category, gametypeId, hand_start_time,
# hard-code styleKey as 'A000000' (all-time cache, no key) for now # hard-code styleKey as 'A000000' (all-time cache, no key) for now
styleKey = 'A000000' styleKey = 'A000000'
#print "storeHudCache, len(playerIds)=", len(playerIds), " len(vpip)=" \ #print "storeHudCache2, len(playerIds)=", len(playerIds), " len(vpip)=" \
#, len(hudImportData['street0VPI']), " len(totprof)=", len(hudImportData['totalProfit']) #, len(hudImportData['street0VPI']), " len(totprof)=", len(hudImportData['totalProfit'])
for player in xrange(len(playerIds)): for player in xrange(len(playerIds)):
@ -2292,189 +2287,6 @@ VALUES (%s, %s, %s, %s, %s, %s,
pass pass
# else: # else:
# print "todo: implement storeHudCache for stud base" # print "todo: implement storeHudCache for stud base"
#end def storeHudCache
def storeHudCache2(backend, cursor, base, category, gametypeId, hand_start_time, playerIds, hudImportData):
"""Modified version aiming for more speed ..."""
# if (category=="holdem" or category=="omahahi" or category=="omahahilo"):
if use_date_in_hudcache:
#print "key =", "d%02d%02d%02d " % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day)
styleKey = "d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day)
else:
# hard-code styleKey as 'A000000' (all-time cache, no key) for now
styleKey = 'A000000'
#print "storeHudCache2, len(playerIds)=", len(playerIds), " len(vpip)=" \
#, len(hudImportData['street0VPI']), " len(totprof)=", len(hudImportData['totalProfit'])
for player in xrange(len(playerIds)):
# Set up a clean row
row=[]
row.append(0)#blank for id
row.append(gametypeId)
row.append(playerIds[player])
row.append(len(playerIds))#seats
for i in xrange(len(hudImportData)+2):
row.append(0)
if base=="hold":
row[4]=hudImportData['position'][player]
else:
row[4]=0
row[5]=1 #tourneysGametypeId
row[6]+=1 #HDs
if hudImportData['street0VPI'][player]: row[7]+=1
if hudImportData['street0Aggr'][player]: row[8]+=1
if hudImportData['street0_3BChance'][player]: row[9]+=1
if hudImportData['street0_3BDone'][player]: row[10]+=1
if hudImportData['street1Seen'][player]: row[11]+=1
if hudImportData['street2Seen'][player]: row[12]+=1
if hudImportData['street3Seen'][player]: row[13]+=1
if hudImportData['street4Seen'][player]: row[14]+=1
if hudImportData['sawShowdown'][player]: row[15]+=1
if hudImportData['street1Aggr'][player]: row[16]+=1
if hudImportData['street2Aggr'][player]: row[17]+=1
if hudImportData['street3Aggr'][player]: row[18]+=1
if hudImportData['street4Aggr'][player]: row[19]+=1
if hudImportData['otherRaisedStreet1'][player]: row[20]+=1
if hudImportData['otherRaisedStreet2'][player]: row[21]+=1
if hudImportData['otherRaisedStreet3'][player]: row[22]+=1
if hudImportData['otherRaisedStreet4'][player]: row[23]+=1
if hudImportData['foldToOtherRaisedStreet1'][player]: row[24]+=1
if hudImportData['foldToOtherRaisedStreet2'][player]: row[25]+=1
if hudImportData['foldToOtherRaisedStreet3'][player]: row[26]+=1
if hudImportData['foldToOtherRaisedStreet4'][player]: row[27]+=1
if hudImportData['wonWhenSeenStreet1'][player]!=0.0: row[28]+=hudImportData['wonWhenSeenStreet1'][player]
if hudImportData['wonAtSD'][player]!=0.0: row[29]+=hudImportData['wonAtSD'][player]
if hudImportData['stealAttemptChance'][player]: row[30]+=1
if hudImportData['stealAttempted'][player]: row[31]+=1
if hudImportData['foldBbToStealChance'][player]: row[32]+=1
if hudImportData['foldedBbToSteal'][player]: row[33]+=1
if hudImportData['foldSbToStealChance'][player]: row[34]+=1
if hudImportData['foldedSbToSteal'][player]: row[35]+=1
if hudImportData['street1CBChance'][player]: row[36]+=1
if hudImportData['street1CBDone'][player]: row[37]+=1
if hudImportData['street2CBChance'][player]: row[38]+=1
if hudImportData['street2CBDone'][player]: row[39]+=1
if hudImportData['street3CBChance'][player]: row[40]+=1
if hudImportData['street3CBDone'][player]: row[41]+=1
if hudImportData['street4CBChance'][player]: row[42]+=1
if hudImportData['street4CBDone'][player]: row[43]+=1
if hudImportData['foldToStreet1CBChance'][player]: row[44]+=1
if hudImportData['foldToStreet1CBDone'][player]: row[45]+=1
if hudImportData['foldToStreet2CBChance'][player]: row[46]+=1
if hudImportData['foldToStreet2CBDone'][player]: row[47]+=1
if hudImportData['foldToStreet3CBChance'][player]: row[48]+=1
if hudImportData['foldToStreet3CBDone'][player]: row[49]+=1
if hudImportData['foldToStreet4CBChance'][player]: row[50]+=1
if hudImportData['foldToStreet4CBDone'][player]: row[51]+=1
#print "player=", player
#print "len(totalProfit)=", len(hudImportData['totalProfit'])
if hudImportData['totalProfit'][player]:
row[52]+=hudImportData['totalProfit'][player]
if hudImportData['street1CheckCallRaiseChance'][player]: row[53]+=1
if hudImportData['street1CheckCallRaiseDone'][player]: row[54]+=1
if hudImportData['street2CheckCallRaiseChance'][player]: row[55]+=1
if hudImportData['street2CheckCallRaiseDone'][player]: row[56]+=1
if hudImportData['street3CheckCallRaiseChance'][player]: row[57]+=1
if hudImportData['street3CheckCallRaiseDone'][player]: row[58]+=1
if hudImportData['street4CheckCallRaiseChance'][player]: row[59]+=1
if hudImportData['street4CheckCallRaiseDone'][player]: row[60]+=1
# Try to do the update first:
num = cursor.execute("""UPDATE HudCache
SET HDs=HDs+%s, street0VPI=street0VPI+%s, street0Aggr=street0Aggr+%s,
street0_3B4BChance=street0_3B4BChance+%s, street0_3B4BDone=street0_3B4BDone+%s,
street1Seen=street1Seen+%s, street2Seen=street2Seen+%s, street3Seen=street3Seen+%s,
street4Seen=street4Seen+%s, sawShowdown=sawShowdown+%s,
street1Aggr=street1Aggr+%s, street2Aggr=street2Aggr+%s, street3Aggr=street3Aggr+%s,
street4Aggr=street4Aggr+%s, otherRaisedStreet1=otherRaisedStreet1+%s,
otherRaisedStreet2=otherRaisedStreet2+%s, otherRaisedStreet3=otherRaisedStreet3+%s,
otherRaisedStreet4=otherRaisedStreet4+%s,
foldToOtherRaisedStreet1=foldToOtherRaisedStreet1+%s, foldToOtherRaisedStreet2=foldToOtherRaisedStreet2+%s,
foldToOtherRaisedStreet3=foldToOtherRaisedStreet3+%s, foldToOtherRaisedStreet4=foldToOtherRaisedStreet4+%s,
wonWhenSeenStreet1=wonWhenSeenStreet1+%s, wonAtSD=wonAtSD+%s, stealAttemptChance=stealAttemptChance+%s,
stealAttempted=stealAttempted+%s, foldBbToStealChance=foldBbToStealChance+%s,
foldedBbToSteal=foldedBbToSteal+%s,
foldSbToStealChance=foldSbToStealChance+%s, foldedSbToSteal=foldedSbToSteal+%s,
street1CBChance=street1CBChance+%s, street1CBDone=street1CBDone+%s, street2CBChance=street2CBChance+%s,
street2CBDone=street2CBDone+%s, street3CBChance=street3CBChance+%s,
street3CBDone=street3CBDone+%s, street4CBChance=street4CBChance+%s, street4CBDone=street4CBDone+%s,
foldToStreet1CBChance=foldToStreet1CBChance+%s, foldToStreet1CBDone=foldToStreet1CBDone+%s,
foldToStreet2CBChance=foldToStreet2CBChance+%s, foldToStreet2CBDone=foldToStreet2CBDone+%s,
foldToStreet3CBChance=foldToStreet3CBChance+%s,
foldToStreet3CBDone=foldToStreet3CBDone+%s, foldToStreet4CBChance=foldToStreet4CBChance+%s,
foldToStreet4CBDone=foldToStreet4CBDone+%s, totalProfit=totalProfit+%s,
street1CheckCallRaiseChance=street1CheckCallRaiseChance+%s,
street1CheckCallRaiseDone=street1CheckCallRaiseDone+%s, street2CheckCallRaiseChance=street2CheckCallRaiseChance+%s,
street2CheckCallRaiseDone=street2CheckCallRaiseDone+%s, street3CheckCallRaiseChance=street3CheckCallRaiseChance+%s,
street3CheckCallRaiseDone=street3CheckCallRaiseDone+%s, street4CheckCallRaiseChance=street4CheckCallRaiseChance+%s,
street4CheckCallRaiseDone=street4CheckCallRaiseDone+%s
WHERE gametypeId+0=%s
AND playerId=%s
AND activeSeats=%s
AND position=%s
AND tourneyTypeId+0=%s
AND styleKey=%s
""", (row[6], row[7], row[8], row[9], row[10],
row[11], row[12], row[13], row[14], row[15],
row[16], row[17], row[18], row[19], row[20],
row[21], row[22], row[23], row[24], row[25],
row[26], row[27], row[28], row[29], row[30],
row[31], row[32], row[33], row[34], row[35],
row[36], row[37], row[38], row[39], row[40],
row[41], row[42], row[43], row[44], row[45],
row[46], row[47], row[48], row[49], row[50],
row[51], row[52], row[53], row[54], row[55],
row[56], row[57], row[58], row[59], row[60],
row[1], row[2], row[3], str(row[4]), row[5], styleKey))
# Test statusmessage to see if update worked, do insert if not
#print "storehud2, upd num =", num
if ( (backend == PGSQL and cursor.statusmessage != "UPDATE 1")
or (backend == MYSQL_INNODB and num == 0) ):
#print "playerid before insert:",row[2]," num = ", num
cursor.execute("""INSERT INTO HudCache
(gametypeId, playerId, activeSeats, position, tourneyTypeId, styleKey,
HDs, street0VPI, street0Aggr, street0_3BChance, street0_3BDone,
street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown,
street1Aggr, street2Aggr, street3Aggr, street4Aggr, otherRaisedStreet1,
otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4, foldToOtherRaisedStreet1, foldToOtherRaisedStreet2,
foldToOtherRaisedStreet3, foldToOtherRaisedStreet4, wonWhenSeenStreet1, wonAtSD, stealAttemptChance,
stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal,
street1CBChance, street1CBDone, street2CBChance, street2CBDone, street3CBChance,
street3CBDone, street4CBChance, street4CBDone, foldToStreet1CBChance, foldToStreet1CBDone,
foldToStreet2CBChance, foldToStreet2CBDone, foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance,
foldToStreet4CBDone, totalProfit, street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance,
street2CheckCallRaiseDone, street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone)
VALUES (%s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s)"""
, (row[1], row[2], row[3], row[4], row[5], styleKey, row[6], row[7], row[8], row[9], row[10]
,row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19], row[20]
,row[21], row[22], row[23], row[24], row[25], row[26], row[27], row[28], row[29], row[30]
,row[31], row[32], row[33], row[34], row[35], row[36], row[37], row[38], row[39], row[40]
,row[41], row[42], row[43], row[44], row[45], row[46], row[47], row[48], row[49], row[50]
,row[51], row[52], row[53], row[54], row[55], row[56], row[57], row[58], row[59], row[60]) )
#print "hopefully inserted hud data line: ", cursor.statusmessage
# message seems to be "INSERT 0 1"
else:
#print "updated(2) hud data line"
pass
# else:
# print "todo: implement storeHudCache for stud base"
#end def storeHudCache2 #end def storeHudCache2
def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime): def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime):

271
pyfpdb/interlocks.py Executable file
View File

@ -0,0 +1,271 @@
# Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py
# Thanks JJ!
import sys
import os, os.path
import subprocess
import time
import signal
import base64
InterProcessLock = None
"""
Just use me like a thread lock. acquire() / release() / locked()
Differences compared to thread locks:
1. By default, acquire()'s wait parameter is false.
2. When acquire fails, SingleInstanceError is thrown instead of simply returning false.
3. acquire() can take a 3rd parameter retry_time, which, if wait is True, tells the locking
mechanism how long to sleep between retrying the lock. Has no effect for unix/InterProcessLockFcntl.
Differences in fpdb version to JJ's original:
1. Changed acquire() to return false like other locks
2. Made acquire fail if same process already has the lock
"""
class SingleInstanceError(RuntimeError):
"Thrown when you try to acquire an InterProcessLock and another version of the process is already running."
class InterProcessLockBase:
def __init__(self, name=None ):
self._has_lock = False
if not name:
name = sys.argv[0]
self.name = name
def getHashedName(self):
return base64.b64encode(self.name).replace('=','')
def acquire_impl(self, wait): abstract
def acquire(self, wait=False, retry_time=1):
if self._has_lock: # make sure 2nd acquire in same process fails
return False
while not self._has_lock:
try:
self.acquire_impl(wait)
self._has_lock = True
#print 'i have the lock'
except SingleInstanceError:
if not wait:
# raise # change back to normal acquire functionality, sorry JJ!
return False
time.sleep(retry_time)
return True
def release(self):
self.release_impl()
self._has_lock = False
def locked(self):
if self._has_lock:
return True
try:
self.acquire()
self.release()
return False
except SingleInstanceError:
return True
LOCK_FILE_DIRECTORY = '/tmp'
class InterProcessLockFcntl(InterProcessLockBase):
def __init__(self, name=None):
InterProcessLockBase.__init__(self, name)
self.lockfd = 0
self.lock_file_name = os.path.join(LOCK_FILE_DIRECTORY, self.getHashedName() + '.lck')
assert(os.path.isdir(LOCK_FILE_DIRECTORY))
# This is the suggested way to get a safe file name, but I like having a descriptively named lock file.
def getHashedName(self):
import re
bad_filename_character_re = re.compile(r'/\?<>\\\:;\*\|\'\"\^=\.\[\]')
return bad_filename_character_re.sub('_',self.name)
def acquire_impl(self, wait):
self.lockfd = open(self.lock_file_name, 'w')
fcntrl_options = fcntl.LOCK_EX
if not wait:
fcntrl_options |= fcntl.LOCK_NB
try:
fcntl.flock(self.lockfd, fcntrl_options)
except IOError:
self.lockfd.close()
self.lockfd = 0
raise SingleInstanceError('Could not acquire exclusive lock on '+self.lock_file_name)
def release_impl(self):
fcntl.lockf(self.lockfd, fcntl.LOCK_UN)
self.lockfd.close()
self.lockfd = 0
try:
os.unlink(self.lock_file_name)
except IOError:
# We don't care about the existence of the file too much here. It's the flock() we care about,
# And that should just go away magically.
pass
class InterProcessLockWin32(InterProcessLockBase):
def __init__(self, name=None):
InterProcessLockBase.__init__(self, name)
self.mutex = None
def acquire_impl(self,wait):
self.mutex = win32event.CreateMutex(None, 0, self.getHashedName())
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
self.mutex.Close()
self.mutex = None
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
def release_impl(self):
self.mutex.Close()
class InterProcessLockSocket(InterProcessLockBase):
def __init__(self, name=None):
InterProcessLockBase.__init__(self, name)
self.socket = None
self.portno = 65530 - abs(self.getHashedName().__hash__()) % 32749
def acquire_impl(self, wait):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.socket.bind(('127.0.0.1', self.portno))
except socket.error:
self.socket.close()
self.socket = None
raise SingleInstanceError('Could not acquire exclusive lock on ' + self.name)
def release_impl(self):
self.socket.close()
self.socket = None
# Set InterProcessLock to the correct type given the sysem parameters available
try:
import fcntl
InterProcessLock = InterProcessLockFcntl
except ImportError:
try:
import win32event
import win32api
import winerror
InterProcessLock = InterProcessLockWin32
except ImportError:
import socket
InterProcessLock = InterProcessLockSocket
def test_construct():
"""
# Making the name of the test unique so it can be executed my multiple users on the same machine.
>>> test_name = 'InterProcessLockTest' +str(os.getpid()) + str(time.time())
>>> lock1 = InterProcessLock(name=test_name)
>>> lock1.acquire()
>>> lock2 = InterProcessLock(name=test_name)
>>> lock3 = InterProcessLock(name=test_name)
# Since lock1 is locked, other attempts to acquire it fail.
>>> lock2.acquire()
Traceback (most recent call last):
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> lock3.acquire()
Traceback (most recent call last):
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release the lock and let lock2 have it.
>>> lock1.release()
>>> lock2.acquire()
>>> lock3.acquire()
Traceback (most recent call last):
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release it and give it back to lock1
>>> lock2.release()
>>> lock1.acquire()
>>> lock2.acquire()
Traceback (most recent call last):
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Test lock status
>>> lock2.locked()
True
>>> lock3.locked()
True
>>> lock1.locked()
True
>>> lock1.release()
>>> lock2.locked()
False
>>> lock3.locked()
False
>>> lock1.locked()
False
>>> if os.name == 'posix':
... def os_independent_kill(pid):
... import signal
... os.kill(pid, signal.SIGKILL)
... else:
... assert(os.name == 'nt')
... def os_independent_kill(pid):
... ''' http://www.python.org/doc/faq/windows/#how-do-i-emulate-os-kill-in-windows '''
... import win32api
... import win32con
... import pywintypes
... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
... return (0 != win32api.TerminateProcess(handle, 0))
# Test to acquire the lock in another process.
>>> def execute(cmd):
... cmd = 'import time;' + cmd + 'time.sleep(10);'
... process = subprocess.Popen([sys.executable, '-c', cmd])
... pid = process.pid
... time.sleep(2) # quick hack, but we test synchronization in the end
... return pid
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire()
Traceback (most recent call last):
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid)
>>> time.sleep(1)
>>> lock1.acquire()
>>> lock1.release()
# Testing wait
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire()
Traceback (most recent call last):
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid)
>>> lock1.acquire(True)
>>> lock1.release()
"""
pass
if __name__=='__main__':
import doctest
doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL)