diff --git a/pyfpdb/Card.py b/pyfpdb/Card.py index 6ab9b62d..8e99599b 100755 --- a/pyfpdb/Card.py +++ b/pyfpdb/Card.py @@ -94,29 +94,32 @@ def cardFromValueSuit(value, suit): elif suit == 's': return(value+38) else: return(0) -def valueSuitFromCard(card): - """ Function to convert a card stored in the database (int 0-52) into value - and suit like 9s, 4c etc """ - if card < 0 or card > 52 or not card: - return('') - else: - return( ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', 'Ah' +suitFromCardList = ['', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', 'Ah' , '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d', 'Td', 'Jd', 'Qd', 'Kd', 'Ad' , '2c', '3c', '4c', '5c', '6c', '7c', '8c', '9c', 'Tc', 'Jc', 'Qc', 'Kc', 'Ac' , '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', 'Ts', 'Js', 'Qs', 'Ks', 'As' - ][card] ) + ] +def valueSuitFromCard(card): + """ Function to convert a card stored in the database (int 0-52) into value + and suit like 9s, 4c etc """ + global suitFromCardList + if card < 0 or card > 52 or not card: + return('') + else: + return suitFromCardList[card] -def encodeCard(cardString): - """Take a card string (Ah) and convert it to the db card code (1).""" - try: - return {'2h': 1, '3h': 2, '4h': 3, '5h': 4, '6h': 5, '7h': 6, '8h': 7, '9h': 8, 'Th': 9, 'Jh': 10, 'Qh': 11, 'Kh': 12, 'Ah': 13, +encodeCardList = {'2h': 1, '3h': 2, '4h': 3, '5h': 4, '6h': 5, '7h': 6, '8h': 7, '9h': 8, 'Th': 9, 'Jh': 10, 'Qh': 11, 'Kh': 12, 'Ah': 13, '2d': 14, '3d': 15, '4d': 16, '5d': 17, '6d': 18, '7d': 19, '8d': 20, '9d': 21, 'Td': 22, 'Jd': 23, 'Qd': 24, 'Kd': 25, 'Ad': 26, '2c': 27, '3c': 28, '4c': 29, '5c': 30, '6c': 31, '7c': 32, '8c': 33, '9c': 34, 'Tc': 35, 'Jc': 36, 'Qc': 27, 'Kc': 38, 'Ac': 39, '2s': 40, '3s': 41, '4s': 42, '5s': 43, '6s': 44, '7s': 45, '8s': 46, '9s': 47, 'Ts': 48, 'Js': 49, 'Qs': 50, 'Ks': 51, 'As': 52, ' ': 0 - }[cardString] - except: - return 0 # everthing that isn't known is a unknown! + } + +def encodeCard(cardString): + """Take a card string (Ah) and convert it to the db card code (1).""" + global encodeCardList + if cardString not in encodeCardList: return 0 + return encodeCardList[cardString] if __name__ == '__main__': print "fpdb card encoding(same as pokersource)" diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 9e0007a3..7c2b9ecc 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -27,10 +27,11 @@ Create and manage the database objects. import sys import traceback from datetime import datetime, date, time, timedelta -from time import time, strftime +from time import time, strftime, sleep import string import re import logging +import Queue # pyGTK modules @@ -67,7 +68,7 @@ class Database: , {'tab':'Hands', 'col':'gametypeId', 'drop':0} # mct 22/3/09 , {'tab':'HandsPlayers', 'col':'handId', 'drop':0} # not needed, handled by fk , {'tab':'HandsPlayers', 'col':'playerId', 'drop':0} # not needed, handled by fk - , {'tab':'HandsPlayers', 'col':'tourneysTypeId', 'drop':0} + , {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0} , {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0} , {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0} ] @@ -181,57 +182,24 @@ class Database: else: self.sql = sql + self.pcache = None # PlayerId cache + self.cachemiss = 0 # Delete me later - using to count player cache misses + self.cachehit = 0 # Delete me later - using to count player cache hits + # config while trying out new hudcache mechanism self.use_date_in_hudcache = True - # To add to config: - 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 - # by a 40 minute gap and then more hands on same table that is - # a new session) - self.hud_style = 'T' # A=All-time - # S=Session - # T=timed (last n days) - # Future values may also include: - # H=Hands (last n hands) - self.hud_hands = 2000 # Max number of hands 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_hero_style = 'T' # Duplicate set of vars just for hero - not used yet. + #self.hud_hero_hands = 2000 # Idea is that you might want all-time stats for others + #self.hud_hero_days = 30 # but last T days or last H hands for yourself - self.hud_hero_style = 'T' # Duplicate set of vars just for hero - self.hud_hero_hands = 2000 - self.hud_hero_days = 30 + # vars for hand ids or dates fetched according to above config: + self.hand_1day_ago = 0 # max hand id more than 24 hrs earlier than now + self.date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) + self.date_nhands_ago = {} # dates N hands ago per player - not used yet 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.cursor.execute(self.sql.query['get_hand_1day_ago']) - row = self.cursor.fetchone() - if row and row[0]: - self.hand_1day_ago = row[0] - #print "hand 1day ago =", self.hand_1day_ago - - # self.date_ndays_ago used if hud_style = 'T' - d = timedelta(days=self.hud_days) - now = datetime.utcnow() - d - self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day) - - # self.hand_nhands_ago is used for fetching stats for last n hands (hud_style = 'H') - # This option not used yet - 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 @@ -364,22 +332,67 @@ class Database: winners[row[0]] = row[1] return winners - def get_stats_from_hand(self, hand, aggregate = False): - if self.hud_style == 'S': + def init_hud_stat_vars(self, hud_days): + """Initialise variables used by Hud to fetch stats.""" + + try: + # self.hand_1day_ago used to fetch stats for current session (i.e. if hud_style = 'S') + self.hand_1day_ago = 1 + c = self.get_cursor() + c.execute(self.sql.query['get_hand_1day_ago']) + row = c.fetchone() + if row and row[0]: + self.hand_1day_ago = row[0] + #print "hand 1day ago =", self.hand_1day_ago + + # self.date_ndays_ago used if hud_style = 'T' + d = timedelta(days=hud_days) + now = datetime.utcnow() - d + self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day) + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) + + def init_player_hud_stat_vars(self, playerid): + # not sure if this is workable, to be continued ... + try: + # self.date_nhands_ago is used for fetching stats for last n hands (hud_style = 'H') + # This option not used yet - needs to be called for each player :-( + self.date_nhands_ago[str(playerid)] = 'd000000' + + # should use aggregated version of query if appropriate + c.execute(self.sql.query['get_date_nhands_ago'], (self.hud_hands, playerid)) + row = c.fetchone() + if row and row[0]: + self.date_nhands_ago[str(playerid)] = row[0] + c.close() + print "date n hands ago = " + self.date_nhands_ago[str(playerid)] + "(playerid "+str(playerid)+")" + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) + + def get_stats_from_hand(self, hand, aggregate = False, hud_style = 'A', agg_bb_mult = 100): + if hud_style == 'S': + return( self.get_stats_from_hand_session(hand) ) - else: # self.hud_style == A + + else: # hud_style == A + + if hud_style == 'T': + stylekey = self.date_ndays_ago + #elif hud_style == 'H': + # stylekey = date_nhands_ago needs array by player here ... + else: # assume A (all-time) + stylekey = '0000000' # all stylekey values should be higher than this + if aggregate: query = 'get_stats_from_hand_aggregated' + subs = (hand, stylekey, agg_bb_mult, agg_bb_mult) else: query = 'get_stats_from_hand' - - if self.hud_style == 'T': - stylekey = self.date_ndays_ago - else: # assume A (all-time) - stylekey = '0000000' # all stylekey values should be higher than this + subs = (hand, stylekey) - subs = (hand, hand, stylekey) - #print "get stats: hud style =", self.hud_style, "subs =", subs + #print "get stats: hud style =", hud_style, "query =", query, "subs =", subs c = self.connection.cursor() # now get the stats @@ -398,17 +411,14 @@ class Database: # uses query on handsplayers instead of hudcache to get stats on just this session def get_stats_from_hand_session(self, hand): - if self.hud_style == 'S': - query = self.sql.query['get_stats_from_hand_session'] - if self.db_server == 'mysql': - query = query.replace("", 'signed ') - else: - query = query.replace("", '') - else: # self.hud_style == A - return None + query = self.sql.query['get_stats_from_hand_session'] + if self.db_server == 'mysql': + query = query.replace("", 'signed ') + else: + query = query.replace("", '') subs = (self.hand_1day_ago, hand) - c = self.connection.cursor() + c = self.get_cursor() # now get the stats #print "sess_stats: subs =", subs, "subs[0] =", subs[0] @@ -442,7 +452,7 @@ class Database: def get_player_id(self, config, site, player_name): c = self.connection.cursor() - c.execute(self.sql.query['get_player_id'], {'player': player_name, 'site': site}) + c.execute(self.sql.query['get_player_id'], (player_name, site)) row = c.fetchone() if row: return row[0] @@ -469,23 +479,19 @@ class Database: ,action_types, allIns, action_amounts, actionNos, hudImportData, maxSeats, tableName ,seatNos): - try: - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) + fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - hands_id = self.storeHands(self.backend, site_hand_no, gametype_id - ,hand_start_time, names, tableName, maxSeats, hudImportData - ,(None, None, None, None, None), (None, None, None, None, None)) + hands_id = self.storeHands(self.backend, 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 = self.store_hands_players_stud(self.backend, hands_id, player_ids - ,start_cashes, antes, card_values - ,card_suits, winnings, rakes, seatNos) + #print "before calling store_hands_players_stud, antes:", antes + hands_players_ids = self.store_hands_players_stud(self.backend, hands_id, player_ids + ,start_cashes, antes, card_values + ,card_suits, winnings, rakes, seatNos) - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) - except: - print "ring_stud error: " + str(sys.exc_value) # in case exception doesn't get printed - raise fpdb_simple.FpdbError("ring_stud error: " + str(sys.exc_value)) + if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': + self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) return hands_id #end def ring_stud @@ -496,30 +502,26 @@ class Database: ,action_amounts, actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a holdem/omaha hand into the database""" - try: - t0 = time() - #print "in ring_holdem_omaha" - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - t1 = time() - fpdb_simple.fill_board_cards(board_values, board_suits) - t2 = time() + t0 = time() + #print "in ring_holdem_omaha" + 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 = self.storeHands(self.backend, 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 = self.store_hands_players_holdem_omaha( - self.backend, category, hands_id, player_ids, start_cashes - , positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData) - t4 = time() - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) - t5 = time() - #print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4) - except: - print "ring_holdem_omaha error: " + str(sys.exc_value) # in case exception doesn't get printed - raise fpdb_simple.FpdbError("ring_holdem_omaha error: " + str(sys.exc_value)) + hands_id = self.storeHands(self.backend, 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 = self.store_hands_players_holdem_omaha( + self.backend, category, hands_id, player_ids, start_cashes + , positions, card_values, card_suits, winnings, rakes, seatNos, hudImportData) + t4 = time() + if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': + self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) + t5 = time() + #print "fills=(%4.3f) saves=(%4.3f,%4.3f,%4.3f)" % (t2-t0, t3-t2, t4-t3, t5-t4) return hands_id #end def ring_holdem_omaha @@ -532,28 +534,24 @@ class Database: ,actionNos, hudImportData, maxSeats, tableName, seatNos): """stores a tourney holdem/omaha hand into the database""" - try: - fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) - fpdb_simple.fill_board_cards(board_values, board_suits) + fpdb_simple.fillCardArrays(len(names), base, category, card_values, card_suits) + fpdb_simple.fill_board_cards(board_values, board_suits) - tourney_id = self.store_tourneys(tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start) - tourneys_players_ids = self.store_tourneys_players(tourney_id, player_ids, payin_amounts, ranks, winnings) + tourney_id = self.store_tourneys(tourneyTypeId, siteTourneyNo, entries, prizepool, tourney_start) + tourneys_players_ids = self.store_tourneys_players(tourney_id, player_ids, payin_amounts, ranks, winnings) - hands_id = self.storeHands(self.backend, site_hand_no, gametype_id - ,hand_start_time, names, tableName, maxSeats - ,hudImportData, board_values, board_suits) + hands_id = self.storeHands(self.backend, site_hand_no, gametype_id + ,hand_start_time, names, tableName, maxSeats + ,hudImportData, board_values, board_suits) - hands_players_ids = self.store_hands_players_holdem_omaha_tourney( - self.backend, category, hands_id, player_ids, start_cashes, positions - , card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids - , hudImportData) + hands_players_ids = self.store_hands_players_holdem_omaha_tourney( + self.backend, category, hands_id, player_ids, start_cashes, positions + , card_values, card_suits, winnings, rakes, seatNos, tourneys_players_ids + , hudImportData) - #print "tourney holdem, backend=%d" % backend - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) - except: - print "tourney_holdem_omaha error: " + str(sys.exc_value) # in case exception doesn't get printed - raise fpdb_simple.FpdbError("tourney_holdem_omaha error: " + str(sys.exc_value)) + #print "tourney holdem, backend=%d" % backend + if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': + self.storeHudCache(self.backend, base, category, gametype_id, hand_start_time, player_ids, hudImportData) return hands_id #end def tourney_holdem_omaha @@ -565,26 +563,22 @@ class Database: ,actionNos, hudImportData, maxSeats, tableName, seatNos): #stores a tourney stud/razz hand into the database - try: - fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits) + fpdb_simple.fillCardArrays(len(names), base, category, cardValues, cardSuits) - tourney_id = self.store_tourneys(tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime) + tourney_id = self.store_tourneys(tourneyTypeId, siteTourneyNo, entries, prizepool, tourneyStartTime) - tourneys_players_ids = self.store_tourneys_players(tourney_id, playerIds, payin_amounts, ranks, winnings) + tourneys_players_ids = self.store_tourneys_players(tourney_id, playerIds, payin_amounts, ranks, winnings) - hands_id = self.storeHands( self.backend, siteHandNo, gametypeId - , handStartTime, names, tableName, maxSeats - , hudImportData, board_values, board_suits ) + hands_id = self.storeHands( self.backend, siteHandNo, gametypeId + , handStartTime, names, tableName, maxSeats + , hudImportData, board_values, board_suits ) - hands_players_ids = self.store_hands_players_stud_tourney(self.backend, hands_id - , playerIds, startCashes, antes, cardValues, cardSuits - , winnings, rakes, seatNos, tourneys_players_ids) + hands_players_ids = self.store_hands_players_stud_tourney(self.backend, hands_id + , playerIds, startCashes, antes, cardValues, cardSuits + , winnings, rakes, seatNos, tourneys_players_ids) - if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': - self.storeHudCache(self.backend, base, category, gametypeId, hand_start_time, playerIds, hudImportData) - except: - print "tourney_stud error: " + str(sys.exc_value) # in case exception doesn't get printed - raise fpdb_simple.FpdbError("tourney_stud error: " + str(sys.exc_value)) + if 'dropHudCache' not in settings or settings['dropHudCache'] != 'drop': + self.storeHudCache(self.backend, base, category, gametypeId, hand_start_time, playerIds, hudImportData) return hands_id #end def tourney_stud @@ -613,7 +607,7 @@ class Database: "AND referenced_column_name = %s ", (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) cons = c.fetchone() - print "preparebulk find fk: cons=", cons + #print "preparebulk find fk: cons=", cons if cons: print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol'] try: @@ -740,8 +734,8 @@ class Database: if self.backend == self.MYSQL_INNODB: print "creating mysql index ", idx['tab'], idx['col'] try: - c.execute( "alter table %s add index %s(%s)" - , (idx['tab'],idx['col'],idx['col']) ) + s = "alter table %s add index %s(%s)" % (idx['tab'],idx['col'],idx['col']) + c.execute(s) except: print " create fk failed: " + str(sys.exc_info()) elif self.backend == self.PGSQL: @@ -749,9 +743,8 @@ class Database: # mod to use tab_col for index name? print "creating pg index ", idx['tab'], idx['col'] try: - print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col']) - c.execute( "create index %s_%s_idx on %s(%s)" - % (idx['tab'], idx['col'], idx['tab'], idx['col']) ) + s = "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col']) + c.execute(s) except: print " create index failed: " + str(sys.exc_info()) else: @@ -820,9 +813,11 @@ class Database: self.fillDefaultData() self.commit() except: - print "Error creating tables: ", str(sys.exc_value) + #print "Error creating tables: ", str(sys.exc_value) + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error creating tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) self.rollback() - raise fpdb_simple.FpdbError( "Error creating tables " + str(sys.exc_value) ) + raise #end def disconnect def drop_tables(self): @@ -852,8 +847,9 @@ class Database: self.commit() except: - print "Error dropping tables: " + str(sys.exc_value) - raise fpdb_simple.FpdbError( "Error dropping tables " + str(sys.exc_value) ) + print "***Error dropping tables: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) + self.rollback() + raise #end def drop_tables def createAllIndexes(self): @@ -866,20 +862,18 @@ class Database: if self.backend == self.MYSQL_INNODB: print "creating mysql index ", idx['tab'], idx['col'] try: - self.get_cursor().execute( "alter table %s add index %s(%s)" - , (idx['tab'],idx['col'],idx['col']) ) + s = "create index %s on %s(%s)" % (idx['col'],idx['tab'],idx['col']) + self.get_cursor().execute(s) except: - pass + print " create idx failed: " + str(sys.exc_info()) elif self.backend == self.PGSQL: # mod to use tab_col for index name? print "creating pg index ", idx['tab'], idx['col'] try: - print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col']) - self.get_cursor().execute( "create index %s_%s_idx on %s(%s)" - % (idx['tab'], idx['col'], idx['tab'], idx['col']) ) + s = "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col']) + self.get_cursor().execute(s) except: - print " ERROR! :-(" - pass + print " create idx failed: " + str(sys.exc_info()) else: print "Only MySQL and Postgres supported so far" return -1 @@ -926,7 +920,10 @@ class Database: c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('Win2day', 'USD')") - c.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") + if self.backend == self.SQLITE: + c.execute("INSERT INTO TourneyTypes VALUES (NULL, 1, 0, 0, 0, 0);") + else: + c.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") #c.execute("""INSERT INTO TourneyTypes # (siteId,buyin,fee,knockout,rebuyOrAddon) VALUES # (1,0,0,0,?)""",(False,) ) @@ -979,6 +976,35 @@ class Database: print "Error during fdb.lock_for_insert:", str(sys.exc_value) #end def lock_for_insert + def getSqlPlayerIDs(self, pnames, siteid): + result = {} + if(self.pcache == None): + self.pcache = LambdaDict(lambda key:self.insertPlayer(key, siteid)) + + for player in pnames: + result[player] = self.pcache[player] + # NOTE: Using the LambdaDict does the same thing as: + #if player in self.pcache: + # #print "DEBUG: cachehit" + # pass + #else: + # self.pcache[player] = self.insertPlayer(player, siteid) + #result[player] = self.pcache[player] + + return result + + def insertPlayer(self, name, site_id): + result = None + c = self.get_cursor() + c.execute ("SELECT id FROM Players WHERE name=%s", (name,)) + tmp=c.fetchall() + if (len(tmp)==0): #new player + c.execute ("INSERT INTO Players (name, siteId) VALUES (%s, %s)", (name, site_id)) + #Get last id might be faster here. + c.execute ("SELECT id FROM Players WHERE name=%s", (name,)) + tmp=c.fetchall() + return tmp[0][0] + def store_the_hand(self, h): """Take a HandToWrite object and store it in the db""" @@ -994,7 +1020,7 @@ class Database: result = self.tourney_holdem_omaha( h.config, h.settings, h.base, h.category, h.siteTourneyNo, h.buyin , h.fee, h.knockout, h.entries, h.prizepool, h.tourneyStartTime - , h.payin_amounts, h.ranks, h.tourneyTypeId, h.siteID, h.siteHandNo + , payin_amounts, ranks, h.tourneyTypeId, h.siteID, h.siteHandNo , h.gametypeID, h.handStartTime, h.names, h.playerIDs, h.startCashes , h.positions, h.cardValues, h.cardSuits, h.boardValues, h.boardSuits , h.winnings, h.rakes, h.actionTypes, h.allIns, h.actionAmounts @@ -1003,13 +1029,13 @@ class Database: result = self.tourney_stud( h.config, h.settings, h.base, h.category, h.siteTourneyNo , h.buyin, h.fee, h.knockout, h.entries, h.prizepool, h.tourneyStartTime - , h.payin_amounts, h.ranks, h.tourneyTypeId, h.siteID, h.siteHandNo + , payin_amounts, ranks, h.tourneyTypeId, h.siteID, h.siteHandNo , h.gametypeID, h.handStartTime, h.names, h.playerIDs, h.startCashes , h.antes, h.cardValues, h.cardSuits, h.winnings, h.rakes, h.actionTypes , h.allIns, h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats , h.tableName, h.seatNos) else: - raise fpself.simple.Fpself.rror("unrecognised category") + raise fpdb_simple.FpdbError("unrecognised category") else: if h.base == "hold": result = self.ring_holdem_omaha( @@ -1027,11 +1053,13 @@ class Database: , h.actionAmounts, h.actionNos, h.hudImportData, h.maxSeats, h.tableName , h.seatNos) else: - raise fpself.simple.Fpself.rror ("unrecognised category") - self.commit() + raise fpdb_simple.FpdbError("unrecognised category") except: print "Error storing hand: " + str(sys.exc_value) self.rollback() + # re-raise the exception so that the calling routine can decide what to do: + # (e.g. a write thread might try again) + raise return result #end def store_the_hand @@ -1576,8 +1604,85 @@ class Database: #end def store_tourneys_players + # read HandToWrite objects from q and insert into database + def insert_queue_hands(self, q, maxwait=10, commitEachHand=True): + n,fails,maxTries,firstWait = 0,0,4,0.1 + sendFinal = False + t0 = time() + while True: + try: + h = q.get(True) # (True,maxWait) has probs if 1st part of import is all dups + except Queue.Empty: + # Queue.Empty exception thrown if q was empty for + # if q.empty() also possible - no point if testing for Queue.Empty exception + # maybe increment a counter and only break after a few times? + # could also test threading.active_count() or look through threading.enumerate() + # so break immediately if no threads, but count up to X exceptions if a writer + # thread is still alive??? + print "queue empty too long - writer stopping ..." + break + except: + print "writer stopping, error reading queue: " + str(sys.exc_info()) + break + #print "got hand", str(h.get_finished()) + + tries,wait,again = 0,firstWait,True + while again: + try: + again = False # set this immediately to avoid infinite loops! + if h.get_finished(): + # all items on queue processed + sendFinal = True + else: + self.store_the_hand(h) + # optional commit, could be every hand / every N hands / every time a + # commit message received?? mark flag to indicate if commits outstanding + if commitEachHand: + self.commit() + n = n + 1 + except: + #print "iqh store error", sys.exc_value # debug + self.rollback() + if re.search('deadlock', str(sys.exc_info()[1]), re.I): + # deadlocks only a problem if hudcache is being updated + tries = tries + 1 + if tries < maxTries and wait < 5: # wait < 5 just to make sure + print "deadlock detected - trying again ..." + sleep(wait) + wait = wait + wait + again = True + else: + print "too many deadlocks - failed to store hand " + h.get_siteHandNo() + if not again: + fails = fails + 1 + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error storing hand: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) + # finished trying to store hand + + # always reduce q count, whether or not this hand was saved ok + q.task_done() + # while True loop + + self.commit() + if sendFinal: + q.task_done() + print "db writer finished: stored %d hands (%d fails) in %.1f seconds" % (n, fails, time()-t0) + # end def insert_queue_hands(): + + + def send_finish_msg(self, q): + try: + h = HandToWrite(True) + q.put(h) + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error sending finish: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) + # end def send_finish_msg(): + + # Class used to hold all the data needed to write a hand to the db # mainParser() in fpdb_parse_logic.py creates one of these and then passes it to +# self.insert_queue_hands() class HandToWrite: @@ -1676,49 +1781,6 @@ class HandToWrite: raise # end def set_hand - def set_ring_holdem_omaha( self, config, settings, base, category, siteHandNo - , gametypeID, handStartTime, names, playerIDs - , startCashes, positions, cardValues, cardSuits - , boardValues, boardSuits, winnings, rakes - , actionTypes, allIns, actionAmounts, actionNos - , hudImportData, maxSeats, tableName, seatNos ): - self.config = config - self.settings = settings - self.base = base - self.category = category - self.siteHandNo = siteHandNo - self.gametypeID = gametypeID - self.handStartTime = handStartTime - self.names = names - self.playerIDs = playerIDs - self.startCashes = startCashes - self.positions = positions - self.cardValues = cardValues - self.cardSuits = cardSuits - self.boardValues = boardValues - self.boardSuits = boardSuits - self.winnings = winnings - self.rakes = rakes - self.actionTypes = actionTypes - self.allIns = allIns - self.actionAmounts = actionAmounts - self.actionNos = actionNos - self.hudImportData = hudImportData - self.maxSeats = maxSeats - self.tableName = tableName - self.seatNos = seatNos - # end def set_ring_holdem_omaha - - def send_ring_holdem_omaha(self, db): - result = db.ring_holdem_omaha( - self.config, self.settings, self.base, self.category, self.siteHandNo - , self.gametypeID, self.handStartTime, self.names, self.playerIDs - , self.startCashes, self.positions, self.cardValues, self.cardSuits - , self.boardValues, self.boardSuits, self.winnings, self.rakes - , self.actionTypes, self.allIns, self.actionAmounts, self.actionNos - , self.hudImportData, self.maxSeats, self.tableName, self.seatNos) - # end def send_ring_holdem_omaha - def get_finished(self): return( self.finished ) # end def get_finished @@ -1738,6 +1800,8 @@ if __name__=="__main__": print "database connection object = ", db_connection.connection print "database type = ", db_connection.type + db_connection.recreate_tables() + h = db_connection.get_last_hand() print "last hand = ", h @@ -1759,3 +1823,17 @@ if __name__=="__main__": print "press enter to continue" sys.stdin.readline() + + +#Code borrowed from http://push.cx/2008/caching-dictionaries-in-python-vs-ruby +class LambdaDict(dict): + def __init__(self, l): + super(LambdaDict, self).__init__() + self.l = l + + def __getitem__(self, key): + if key in self: + return self.get(key) + else: + self.__setitem__(key, self.l(key)) + return self.get(key) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 68bc8d8f..3de15ee5 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -15,6 +15,9 @@ #In the "official" distribution you can find the license in #agpl-3.0.txt in the docs folder of the package. +#fpdb modules +import Card + class DerivedStats(): def __init__(self, hand): self.hand = hand @@ -88,6 +91,70 @@ class DerivedStats(): self.street3CheckCallRaiseDone = 0 self.street4CheckCallRaiseChance = 0 self.street4CheckCallRaiseDone = 0 + + self.hands = {} + self.handsplayers = {} - def getStats(): - pass + def getStats(self, hand): + + for player in hand.players: + self.handsplayers[player[1]] = {} + + self.assembleHands(self.hand) + self.assembleHandsPlayers(self.hand) + + print "hands =", self.hands + print "handsplayers =", self.handsplayers + + def assembleHands(self, hand): + self.hands['tableName'] = hand.tablename + self.hands['siteHandNo'] = hand.handid + self.hands['gametypeId'] = None # Leave None, handled later after checking db + self.hands['handStart'] = hand.starttime # format this! + self.hands['importTime'] = None + self.hands['seats'] = self.countPlayers(hand) + self.hands['maxSeats'] = hand.maxseats + self.hands['boardcard1'] = None + self.hands['boardcard2'] = None + self.hands['boardcard3'] = None + self.hands['boardcard4'] = None + self.hands['boardcard5'] = None + + boardCard = 1 + for street in hand.communityStreets: + for card in hand.board[street]: + self.hands['boardcard%s' % str(boardCard)] = Card.encodeCard(card) + boardCard += 1 + + def assembleHandsPlayers(self, hand): + self.vpip(self.hand) + for i, street in enumerate(hand.actionStreets[1:]): + self.aggr(self.hand, i) + + def vpip(self, hand): + vpipers = set() + for act in hand.actions[hand.actionStreets[1]]: + if act[1] in ('calls','bets', 'raises'): + vpipers.add(act[0]) + + for player in hand.players: + if player[1] in vpipers: + self.handsplayers[player[1]]['vpip'] = True + else: + self.handsplayers[player[1]]['vpip'] = False + self.hands['playersVpi'] = len(vpipers) + + def aggr(self, hand, i): + aggrers = set() + for act in hand.actions[hand.actionStreets[i]]: + if act[1] in ('completes', 'raises'): + aggrers.add(act[0]) + + for player in hand.players: + if player[1] in aggrers: + self.handsplayers[player[1]]['street%sAggr' % i] = True + else: + self.handsplayers[player[1]]['street%sAggr' % i] = False + + def countPlayers(self, hand): + pass \ No newline at end of file diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 3095512c..e86bab67 100755 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -31,10 +31,11 @@ import Configuration import string class GuiAutoImport (threading.Thread): - def __init__(self, settings, config): + def __init__(self, settings, config, sql): """Constructor for GuiAutoImport""" self.settings=settings self.config=config + self.sql = sql imp = self.config.get_import_parameters() @@ -44,7 +45,7 @@ class GuiAutoImport (threading.Thread): self.input_settings = {} self.pipe_to_hud = None - self.importer = fpdb_import.Importer(self,self.settings, self.config) + self.importer = fpdb_import.Importer(self, self.settings, self.config, self.sql) self.importer.setCallHud(True) self.importer.setMinPrint(settings['minPrint']) self.importer.setQuiet(False) @@ -159,29 +160,33 @@ class GuiAutoImport (threading.Thread): # - 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 - widget.set_label(u' _Stop Autoimport ') - if self.pipe_to_hud is None: - if os.name == 'nt': - command = "python HUD_main.py" + " " + self.settings['cl_options'] - bs = 0 # windows is not happy with line buffing here - self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, - universal_newlines=True) - else: - command = os.path.join(sys.path[0], 'HUD_main.py') - cl = [command, ] + string.split(self.settings['cl_options']) - self.pipe_to_hud = subprocess.Popen(cl, bufsize = 1, stdin = subprocess.PIPE, - universal_newlines=True) + try: + print "\nGlobal lock taken ..." + self.doAutoImportBool = True + widget.set_label(u' _Stop Autoimport ') + if self.pipe_to_hud is None: + if os.name == 'nt': + command = "python HUD_main.py" + " " + self.settings['cl_options'] + bs = 0 # windows is not happy with line buffing here + self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, + universal_newlines=True) + else: + command = os.path.join(sys.path[0], 'HUD_main.py') + cl = [command, ] + string.split(self.settings['cl_options']) + self.pipe_to_hud = subprocess.Popen(cl, bufsize = 1, stdin = subprocess.PIPE, + universal_newlines=True) - # Add directories to importer object. - for site in self.input_settings: - self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) - print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0]) - self.do_import() + # Add directories to importer object. + for site in self.input_settings: + self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) + print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0]) + self.do_import() - interval=int(self.intervalEntry.get_text()) - gobject.timeout_add(interval*1000, self.do_import) + interval=int(self.intervalEntry.get_text()) + gobject.timeout_add(interval*1000, self.do_import) + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) else: print "auto-import aborted - global lock not available" else: # toggled off diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index f363be6c..a0df4504 100755 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -21,6 +21,7 @@ import os import sys from time import time from optparse import OptionParser +import traceback # pyGTK modules import pygtk @@ -34,6 +35,9 @@ import Configuration class GuiBulkImport(): + # CONFIGURATION - update these as preferred: + allowThreads = True # set to True to try out the threads field + # not used def import_dir(self): """imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it""" @@ -67,12 +71,19 @@ class GuiBulkImport(): self.importer.setHandsInDB(self.n_hands_in_db) cb_model = self.cb_dropindexes.get_model() cb_index = self.cb_dropindexes.get_active() + cb_hmodel = self.cb_drophudcache.get_model() + cb_hindex = self.cb_drophudcache.get_active() + + self.lab_info.set_text("Importing") # doesn't display :-( if cb_index: self.importer.setDropIndexes(cb_model[cb_index][0]) else: self.importer.setDropIndexes("auto") + if cb_hindex: + self.importer.setDropHudCache(cb_hmodel[cb_hindex][0]) + else: + self.importer.setDropHudCache("auto") sitename = self.cbfilter.get_model()[self.cbfilter.get_active()][0] - self.lab_info.set_text("Importing") self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename) self.importer.setCallHud(False) @@ -81,14 +92,21 @@ class GuiBulkImport(): ttime = time() - starttime if ttime == 0: ttime = 1 - print 'GuiBulkImport.load done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %d/sec'\ - % (stored, dups, partial, errs, ttime, stored / ttime) + print 'GuiBulkImport.load done: Stored: %d \tDuplicates: %d \tPartial: %d \tErrors: %d in %s seconds - %.0f/sec'\ + % (stored, dups, partial, errs, ttime, (stored+0.0) / ttime) self.importer.clearFileList() + if self.n_hands_in_db == 0 and stored > 0: + self.cb_dropindexes.set_sensitive(True) + self.cb_dropindexes.set_active(0) + self.lab_drop.set_sensitive(True) + self.cb_drophudcache.set_sensitive(True) + self.cb_drophudcache.set_active(0) + self.lab_hdrop.set_sensitive(True) self.lab_info.set_text("Import finished") except: - print "bulkimport.loadclicked error: "+str(sys.exc_value) - pass + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) self.settings['global_lock'].release() else: print "bulk-import aborted - global lock not available" @@ -111,7 +129,7 @@ class GuiBulkImport(): self.chooser.show() # Table widget to hold the settings - self.table = gtk.Table(rows = 3, columns = 5, homogeneous = False) + self.table = gtk.Table(rows = 5, columns = 5, homogeneous = False) self.vbox.add(self.table) self.table.show() @@ -126,6 +144,7 @@ class GuiBulkImport(): self.table.attach(self.lab_status, 1, 2, 0, 1, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) self.lab_status.show() self.lab_status.set_justify(gtk.JUSTIFY_RIGHT) + self.lab_status.set_alignment(1.0, 0.5) # spin button - status status_adj = gtk.Adjustment(value=100, lower=0, upper=300, step_incr=10, page_incr=1, page_size=0) #not sure what upper value should be! @@ -137,15 +156,18 @@ class GuiBulkImport(): self.lab_threads = gtk.Label("Number of threads:") self.table.attach(self.lab_threads, 3, 4, 0, 1, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) self.lab_threads.show() - self.lab_threads.set_sensitive(False) + if not self.allowThreads: + self.lab_threads.set_sensitive(False) self.lab_threads.set_justify(gtk.JUSTIFY_RIGHT) + self.lab_threads.set_alignment(1.0, 0.5) # spin button - threads - threads_adj = gtk.Adjustment(value=0, lower=0, upper=10, step_incr=1, page_incr=1, page_size=0) #not sure what upper value should be! + threads_adj = gtk.Adjustment(value=0, lower=0, upper=32, step_incr=1, page_incr=1, page_size=0) #not sure what upper value should be! self.spin_threads = gtk.SpinButton(adjustment=threads_adj, climb_rate=0.0, digits=0) - self.table.attach(self.spin_threads, 4, 5, 0, 1, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) + self.table.attach(self.spin_threads, 4, 5, 0, 1, xpadding = 10, ypadding = 0, yoptions=gtk.SHRINK) self.spin_threads.show() - self.spin_threads.set_sensitive(False) + if not self.allowThreads: + self.spin_threads.set_sensitive(False) # checkbox - fail on error? self.chk_fail = gtk.CheckButton('Fail on error') @@ -157,6 +179,7 @@ class GuiBulkImport(): self.table.attach(self.lab_hands, 1, 2, 1, 2, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) self.lab_hands.show() self.lab_hands.set_justify(gtk.JUSTIFY_RIGHT) + self.lab_hands.set_alignment(1.0, 0.5) # spin button - hands to import hands_adj = gtk.Adjustment(value=0, lower=0, upper=10, step_incr=1, page_incr=1, page_size=0) #not sure what upper value should be! @@ -169,6 +192,7 @@ class GuiBulkImport(): self.table.attach(self.lab_drop, 3, 4, 1, 2, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) self.lab_drop.show() self.lab_drop.set_justify(gtk.JUSTIFY_RIGHT) + self.lab_drop.set_alignment(1.0, 0.5) # ComboBox - drop indexes self.cb_dropindexes = gtk.combo_box_new_text() @@ -181,9 +205,10 @@ class GuiBulkImport(): # label - filter self.lab_filter = gtk.Label("Site filter:") - self.table.attach(self.lab_filter, 2, 3, 2, 3, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) + self.table.attach(self.lab_filter, 1, 2, 2, 3, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) self.lab_filter.show() self.lab_filter.set_justify(gtk.JUSTIFY_RIGHT) + self.lab_filter.set_alignment(1.0, 0.5) # ComboBox - filter self.cbfilter = gtk.combo_box_new_text() @@ -191,21 +216,42 @@ class GuiBulkImport(): print w self.cbfilter.append_text(w) self.cbfilter.set_active(0) - self.table.attach(self.cbfilter, 3, 4, 2, 3, xpadding = 10, ypadding = 0, yoptions=gtk.SHRINK) + self.table.attach(self.cbfilter, 2, 3, 2, 3, xpadding = 10, ypadding = 1, yoptions=gtk.SHRINK) self.cbfilter.show() -# label - info - self.lab_info = gtk.Label() - self.table.attach(self.lab_info, 0, 4, 2, 3, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) - self.lab_info.show() +# label - drop hudcache + self.lab_hdrop = gtk.Label("Drop HudCache:") + self.table.attach(self.lab_hdrop, 3, 4, 2, 3, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) + self.lab_hdrop.show() + self.lab_hdrop.set_justify(gtk.JUSTIFY_RIGHT) + self.lab_hdrop.set_alignment(1.0, 0.5) + +# ComboBox - drop hudcache + self.cb_drophudcache = gtk.combo_box_new_text() + self.cb_drophudcache.append_text('auto') + self.cb_drophudcache.append_text("don't drop") + self.cb_drophudcache.append_text('drop') + self.cb_drophudcache.set_active(0) + self.table.attach(self.cb_drophudcache, 4, 5, 2, 3, xpadding = 10, ypadding = 0, yoptions=gtk.SHRINK) + self.cb_drophudcache.show() # button - Import self.load_button = gtk.Button('Import') # todo: rename variables to import too self.load_button.connect('clicked', self.load_clicked, 'Import clicked') - self.table.attach(self.load_button, 4, 5, 2, 3, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) + self.table.attach(self.load_button, 2, 3, 4, 5, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) self.load_button.show() +# label - spacer (keeps rows 3 & 5 apart) + self.lab_spacer = gtk.Label() + self.table.attach(self.lab_spacer, 3, 5, 3, 4, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) + self.lab_spacer.show() + +# label - info + self.lab_info = gtk.Label() + self.table.attach(self.lab_info, 3, 5, 4, 5, xpadding = 0, ypadding = 0, yoptions=gtk.SHRINK) + self.lab_info.show() + # see how many hands are in the db and adjust accordingly tcursor = self.importer.database.cursor tcursor.execute("Select count(1) from Hands") @@ -217,6 +263,9 @@ class GuiBulkImport(): self.cb_dropindexes.set_active(2) self.cb_dropindexes.set_sensitive(False) self.lab_drop.set_sensitive(False) + self.cb_drophudcache.set_active(2) + self.cb_drophudcache.set_sensitive(False) + self.lab_hdrop.set_sensitive(False) def main(argv=None): """main can also be called in the python interpreter, by supplying the command line as the argument.""" diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example index 3cd592cc..658c0527 100644 --- a/pyfpdb/HUD_config.xml.example +++ b/pyfpdb/HUD_config.xml.example @@ -210,7 +210,7 @@ - + @@ -219,7 +219,7 @@ - + @@ -228,7 +228,7 @@ - + @@ -237,7 +237,7 @@ - + @@ -246,7 +246,7 @@ - + @@ -255,7 +255,7 @@ - + @@ -274,18 +274,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 218707bd..b240b64c 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -31,6 +31,7 @@ Main for FreePokerTools HUD. import sys import os import Options +import traceback (options, sys.argv) = Options.fpdb_options() @@ -55,7 +56,23 @@ import Database import Tables import Hud -aggregate_stats = {"ring": False, "tour": False} # config file! +# To add to config: +aggregate_stats = {"ring": False, "tour": False} # uses agg_bb_mult +hud_style = 'A' # A=All-time + # S=Session + # T=timed (last n days - set hud_days to required value) + # Future values may also include: + # H=Hands (last n hands) +hud_days = 90 # Max number of days from each player to use for hud stats +agg_bb_mult = 100 # 1 = no aggregation. When aggregating stats across levels larger blinds + # must be < (agg_bb_mult * smaller blinds) to be aggregated + # ie. 100 will aggregate almost everything, 2 will probably agg just the + # next higher and lower levels into the current one, try 3/10/30/100 +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 + # by a 40 minute gap and then more hands on same table that is + # a new session) +#hud_hands = 0 # Max number of hands from each player to use for hud stats (not used) class HUD_main(object): """A main() object to own both the read_stdin thread and the gui.""" @@ -144,6 +161,7 @@ class HUD_main(object): # need their own access to the database, but should open their own # if it is required. self.db_connection = Database.Database(self.config, self.db_name, 'temp') + self.db_connection.init_hud_stat_vars(hud_days) tourny_finder = re.compile('(\d+) (\d+)') while 1: # wait for a new hand number on stdin @@ -156,14 +174,18 @@ class HUD_main(object): # if there is a db error, complain, skip hand, and proceed try: (table_name, max, poker_game, type) = self.db_connection.get_table_name(new_hand_id) - stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, aggregate = aggregate_stats[type]) + stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, aggregate_stats[type] + ,hud_style, agg_bb_mult) + cards = self.db_connection.get_cards(new_hand_id) comm_cards = self.db_connection.get_common_cards(new_hand_id) if comm_cards != {}: # stud! cards['common'] = comm_cards['common'] except Exception, err: - print "db error: skipping ", new_hand_id, err - sys.stderr.write("Database error %s in hand %d. Skipping.\n" % (err, int(new_hand_id))) + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print "db error: skipping "+str(new_hand_id)+" "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) + if new_hand_id: # new_hand_id is none if we had an error prior to the store + sys.stderr.write("Database error %s in hand %d. Skipping.\n" % (err, int(new_hand_id))) continue if type == "tour": # hand is from a tournament diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 0dbb9674..4d571b76 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -40,10 +40,12 @@ class Hand(object): LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'} + SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7} def __init__(self, sitename, gametype, handText, builtFrom = "HHC"): self.sitename = sitename + self.siteId = self.SITEIDS[sitename] self.stats = DerivedStats.DerivedStats(self) self.gametype = gametype self.starttime = 0 diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 2ded2c50..bb30e705 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -204,7 +204,9 @@ which it expects to find at self.re_TailSplitHands -- see for e.g. Everleaf.py. logging.info("Unsupported game type: %s" % gametype) if hand: +# uncomment these to calculate some stats # print hand +# hand.stats.getStats(hand) hand.writeHand(self.out_fh) return hand else: diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py index 38307d40..8cd98f20 100644 --- a/pyfpdb/Options.py +++ b/pyfpdb/Options.py @@ -32,6 +32,9 @@ def fpdb_options(): parser.add_option("-c", "--configFile", dest="config", default=None, help="Specifies a configuration file.") + parser.add_option("-r", "--rerunPython", + action="store_true", + help="Indicates program was restarted with a different path (only allowed once).") (options, sys.argv) = parser.parse_args() return (options, sys.argv) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 20ba4319..fcfecb84 100755 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -29,9 +29,11 @@ class PokerStars(HandHistoryConverter): ############################################################ # Class Variables + mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games + sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\x80", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE substitutions = { 'LEGAL_ISO' : "USD|EUR|GBP|CAD", # legal ISO currency codes - 'LS' : "\$" # legal currency symbols + 'LS' : "\$|\x80|\xa3" # legal currency symbols ADD Euro, Sterling, etc HERE } # Static regexes @@ -48,25 +50,32 @@ class PokerStars(HandHistoryConverter): (-\sLevel\s(?P[IVXLC]+)\s)? \(? # open paren of the stakes (?P%(LS)s|)? - (?P[.0-9]+)/%(LS)s? + (?P[.0-9]+)/(%(LS)s)? (?P[.0-9]+) \s?(?P%(LEGAL_ISO)s)? \)\s-\s # close paren of the stakes (?P.*$)""" % substitutions, re.MULTILINE|re.VERBOSE) + + re_PlayerInfo = re.compile(""" + ^Seat\s(?P[0-9]+):\s + (?P.*)\s + \((%(LS)s)?(?P[.0-9]+)\sin\schips\)""" % substitutions, + re.MULTILINE|re.VERBOSE) + + re_HandInfo = re.compile(""" + ^Table\s\'(?P[-\ a-zA-Z\d]+)\'\s + ((?P\d+)-max\s)? + (?P\(Play\sMoney\)\s)? + (Seat\s\#(?P
[-\ a-zA-Z\d]+)\'\s - ((?P\d+)-max\s)? - (?P\(Play\sMoney\)\s)? - (Seat\s\#(?P
[ a-zA-Z]+) - \$?(?P[.0-9]+)/\$?(?P[.0-9]+) - (?P.*) - (?P
[0-9]+):(?P[0-9]+) ET - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)Table (?P
[ a-zA-Z]+)\nSeat (?P