diff --git a/pyfpdb/BetfairToFpdb.py b/pyfpdb/BetfairToFpdb.py index 672e858a..1ccec5d0 100755 --- a/pyfpdb/BetfairToFpdb.py +++ b/pyfpdb/BetfairToFpdb.py @@ -34,12 +34,12 @@ class Betfair(HandHistoryConverter): re_PlayerInfo = re.compile("Seat (?P[0-9]+): (?P.*)\s\(\s(\$(?P[.0-9]+)) \)") re_Board = re.compile(ur"\[ (?P.+) \]") - 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) out_path (default '-' = sys.stdout) 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") self.filetype = "text" self.codepage = "cp1252" diff --git a/pyfpdb/Card.py b/pyfpdb/Card.py index 75c742f9..a35e0e5e 100755 --- a/pyfpdb/Card.py +++ b/pyfpdb/Card.py @@ -57,6 +57,21 @@ def fourStartCards(value1, suit1, value2, suit2, value3, suit3, value4, suit4): #AAKKs #AAKKr # 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) def cardFromValueSuit(value, suit): @@ -70,7 +85,7 @@ def cardFromValueSuit(value, suit): 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: + 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' @@ -80,4 +95,7 @@ def valueSuitFromCard(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)) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 1d93f3f1..e01a6b43 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -86,14 +86,34 @@ class Site: self.aux_window = node.getAttribute("aux_window") self.font = node.getAttribute("font") 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.xpad = node.getAttribute("xpad") + self.ypad = node.getAttribute("ypad") self.layout = {} for layout_node in node.getElementsByTagName('layout'): lo = Layout(layout_node) 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): temp = "Site = " + self.site_name + "\n" for key in dir(self): @@ -119,9 +139,16 @@ class Stat: class Game: def __init__(self, node): self.game_name = node.getAttribute("game_name") - self.db = node.getAttribute("db") self.rows = int( node.getAttribute("rows") ) 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_list = aux_text.split(',') @@ -146,9 +173,10 @@ class Game: def __str__(self): temp = "Game = " + self.game_name + "\n" - temp = temp + " db = %s\n" % self.db temp = temp + " rows = %d\n" % self.rows 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 for stat in self.stats.keys(): @@ -250,6 +278,7 @@ class Config: self.default_config_path = self.get_default_config_path() if file != None: # configuration file path has been passed + file = os.path.expanduser(file) if not os.path.exists(file): print "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 elif string.lower(self.supported_databases[name].db_server) == 'postgresql': 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 return db @@ -630,6 +661,8 @@ class Config: parms["font"] = self.supported_sites[site].font parms["font_size"] = self.supported_sites[site].font_size parms["enabled"] = self.supported_sites[site].enabled + parms["xpad"] = self.supported_sites[site].xpad + parms["ypad"] = self.supported_sites[site].ypad return parms def set_site_parameters(self, site_name, converter = None, decoder = None, @@ -680,9 +713,10 @@ class Config: param = {} if self.supported_games.has_key(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['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 return param diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 9582354a..068f7153 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -27,67 +27,48 @@ Create and manage the database objects. import sys import traceback from datetime import datetime, date, time, timedelta +from time import time, strftime import string # pyGTK modules # FreePokerTools modules +import fpdb_db +import fpdb_simple import Configuration import SQL import Card 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() - if (string.lower(db_params['db-server']) == 'postgresql' or - string.lower(db_params['db-server']) == 'postgres'): - import psycopg2 # posgres via DB-API - import psycopg2.extensions - psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) - - try: - 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.import_options = c.get_import_parameters() self.type = db_params['db-type'] - self.sql = SQL.Sql(game = game, type = self.type) - self.connection.rollback() + self.backend = db_params['db-backend'] + 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: self.hud_style = 'T' # A=All-time # S=Session @@ -95,31 +76,78 @@ class Database: # Future values may also include: # H=Hands (last n hands) 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 # (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) - cur = self.connection.cursor() + self.cursor = self.fdb.cursor - self.hand_1day_ago = 0 - cur.execute(self.sql.query['get_hand_1day_ago']) - row = cur.fetchone() - if row and row[0]: - self.hand_1day_ago = row[0] - #print "hand 1day ago =", self.hand_1day_ago + 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 - 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.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 = 0 # todo - #cur.execute(self.sql.query['get_table_name'], (hand_id, )) - #row = cur.fetchone() + # 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 + + # 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): 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): c = self.connection.cursor() c.execute(self.sql.query['get_table_name'], (hand_id, )) @@ -156,21 +184,13 @@ class Database: def get_cards(self, 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.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(): - cs = ['', '', '', '', '', '', ''] - 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) + cards[row[0]] = row[1:] return cards def get_common_cards(self, hand): @@ -178,12 +198,8 @@ class Database: cards = {} c = self.connection.cursor() c.execute(self.sql.query['get_common_cards'], [hand]) - colnames = [desc[0] for desc in c.description] - for row in c.fetchall(): - s_dict = {} - for name, val in zip(colnames, row): - s_dict[name] = val - cards['common'] = (self.convert_cards(s_dict)) +# row = c.fetchone() + cards['common'] = c.fetchone() return cards def convert_cards(self, d): @@ -211,7 +227,7 @@ class Database: def get_action_from_hand(self, hand_no): action = [ [], [], [], [], [] ] c = self.connection.cursor() - c.execute(self.sql.query['get_action_from_hand'], (hand_no, )) + c.execute(self.sql.query['get_action_from_hand'], (hand_no,)) for row in c.fetchall(): street = row[0] act = row[1:] @@ -222,7 +238,7 @@ class Database: """Returns a hash of winners:amount won, given a hand number.""" winners = {} c = self.connection.cursor() - c.execute(self.sql.query['get_winners_from_hand'], (hand, )) + c.execute(self.sql.query['get_winners_from_hand'], (hand,)) for row in c.fetchall(): winners[row[0]] = row[1] return winners @@ -312,6 +328,159 @@ class Database: else: 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__": c = Configuration.Config() @@ -333,10 +502,10 @@ if __name__=="__main__": for p in stat_dict.keys(): print p, " ", stat_dict[p] -# print "nutOmatics stats:" -# stat_dict = db_connection.get_stats_from_hand(h, hero) -# for p in stat_dict.keys(): -# print p, " ", stat_dict[p] + #print "nutOmatics stats:" + #stat_dict = db_connection.get_stats_from_hand(h, hero) + #for p in stat_dict.keys(): + # print p, " ", stat_dict[p] print "cards =", db_connection.get_cards(u'1') db_connection.close_connection diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index ee4942f1..a7685f90 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -37,7 +37,7 @@ class Everleaf(HandHistoryConverter): re_Board = re.compile(ur"\[ (?P.+) \]") - 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) 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) debugging: if False, pass on partially supported game types. If true, have a go and error...""" 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") self.filetype = "text" self.codepage = "cp1252" @@ -237,11 +237,14 @@ or None if we fail to get the info """ # Also works with Omaha hands. cards = m.group('CARDS') 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: #Not involved in hand hand.involved = False + def readStudPlayerCards(self, hand, street): # lol. see Plymouth.txt logging.warning("Everleaf readStudPlayerCards is only a stub.") @@ -292,7 +295,8 @@ or None if we fail to get the info """ cards = cards.split(', ') player = m.group('PNAME') 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')) diff --git a/pyfpdb/Filters.py b/pyfpdb/Filters.py index 4846b998..9ded56b4 100644 --- a/pyfpdb/Filters.py +++ b/pyfpdb/Filters.py @@ -30,12 +30,11 @@ import fpdb_db import FpdbSQLQueries 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 #print "start of GraphViewer constructor" self.db=db self.cursor=db.cursor - self.settings=settings self.sql=qdict self.conf = config self.display = display @@ -235,7 +234,7 @@ class Filters(threading.Thread): def __set_hero_name(self, w, site): 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): cb = gtk.CheckButton(site) @@ -556,23 +555,12 @@ def main(argv=None): config = Configuration.Config() 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.connect(settings['db-backend'], - settings['db-host'], - settings['db-databaseName'], - settings['db-user'], - settings['db-password']) + db.do_connect(config) qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name()) - i = Filters(db, settings, config, qdict) + i = Filters(db, config, qdict) main_window = gtk.Window() main_window.connect('destroy', destroy) main_window.add(i.get_vbox()) diff --git a/pyfpdb/FpdbRegex.py b/pyfpdb/FpdbRegex.py deleted file mode 100644 index 8bfb9362..00000000 --- a/pyfpdb/FpdbRegex.py +++ /dev/null @@ -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 -# -# 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 - diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py index 598c0868..7a5f0ff5 100644 --- a/pyfpdb/FpdbSQLQueries.py +++ b/pyfpdb/FpdbSQLQueries.py @@ -38,7 +38,9 @@ class FpdbSQLQueries: elif(self.dbname == 'PostgreSQL'): self.query['list_tables'] = """SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'""" elif(self.dbname == 'SQLite'): - self.query['list_tables'] = """ """ + self.query['list_tables'] = """SELECT name FROM sqlite_master + WHERE type='table' + ORDER BY name;""" ################################################################## # Drop Tables - MySQL, PostgreSQL and SQLite all share same syntax @@ -63,8 +65,8 @@ class FpdbSQLQueries: self.query['createSettingsTable'] = """CREATE TABLE Settings (version SMALLINT)""" elif(self.dbname == 'SQLite'): - #Probably doesn't work. - self.query['createSettingsTable'] = """ """ + self.query['createSettingsTable'] = """CREATE TABLE Settings + (version INTEGER) """ ################################ @@ -83,7 +85,10 @@ class FpdbSQLQueries: name varchar(32), currency char(3))""" elif(self.dbname == 'SQLite'): - self.query['createSitesTable'] = """ """ + self.query['createSitesTable'] = """CREATE TABLE Sites ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + currency TEXT NOT NULL)""" ################################ @@ -118,7 +123,19 @@ class FpdbSQLQueries: smallBet int, bigBet int)""" elif(self.dbname == 'SQLite'): - self.query['createGametypesTable'] = """ """ + self.query['createGametypesTable'] = """CREATE TABLE GameTypes ( + id INTEGER PRIMARY KEY, + siteId INTEGER, + type TEXT, + base TEXT, + category TEXT, + limitType TEXT, + hiLo TEXT, + smallBlind INTEGER, + bigBlind INTEGER, + smallBet INTEGER, + bigBet INTEGER, + FOREIGN KEY(siteId) REFERENCES Sites(id) ON DELETE CASCADE)""" ################################ @@ -141,7 +158,13 @@ class FpdbSQLQueries: comment text, commentTs timestamp without time zone)""" elif(self.dbname == 'SQLite'): - self.query['createPlayersTable'] = """ """ + self.query['createPlayersTable'] = """CREATE TABLE Players ( + id INTEGER PRIMARY KEY, + name TEXT, + siteId INTEGER, + comment TEXT, + commentTs BLOB, + FOREIGN KEY(siteId) REFERENCES Sites(id) ON DELETE CASCADE)""" ################################ @@ -245,44 +268,18 @@ class FpdbSQLQueries: comment TEXT, commentTs timestamp without time zone)""" elif(self.dbname == 'SQLite'): - self.query['createHandsTable'] = """ """ - - - ################################ - # Create Gametypes - ################################ - - if(self.dbname == 'MySQL InnoDB'): - self.query['createBoardCardsTable'] = """CREATE TABLE BoardCards ( - id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), - handId BIGINT UNSIGNED NOT NULL, FOREIGN KEY (handId) REFERENCES Hands(id), - card1Value smallint NOT NULL, - card1Suit char(1) NOT NULL, - card2Value smallint NOT NULL, - card2Suit char(1) NOT NULL, - card3Value smallint NOT NULL, - card3Suit char(1) NOT NULL, - card4Value smallint NOT NULL, - card4Suit char(1) NOT NULL, - card5Value smallint NOT NULL, - card5Suit char(1) NOT NULL) - ENGINE=INNODB""" - elif(self.dbname == 'PostgreSQL'): - self.query['createBoardCardsTable'] = """CREATE TABLE BoardCards ( - id BIGSERIAL, PRIMARY KEY (id), - handId BIGINT, FOREIGN KEY (handId) REFERENCES Hands(id), - card1Value smallint, - card1Suit char(1), - card2Value smallint, - card2Suit char(1), - card3Value smallint, - card3Suit char(1), - card4Value smallint, - card4Suit char(1), - card5Value smallint, - card5Suit char(1))""" - elif(self.dbname == 'SQLite'): - self.query['createBoardCardsTable'] = """ """ + self.query['createHandsTable'] = """CREATE TABLE Hands ( + id INTEGER PRIMARY KEY, + tableName TEXT(20), + siteHandNo INTEGER, + gametypeId INTEGER, + handStart BLOB, + importTime BLOB, + seats INTEGER, + maxSeats INTEGER, + comment TEXT, + commentTs BLOB, + FOREIGN KEY(gametypeId) REFERENCES Gametypes(id) ON DELETE CASCADE)""" ################################ @@ -336,7 +333,14 @@ class FpdbSQLQueries: comment TEXT, commentTs timestamp without time zone)""" elif(self.dbname == 'SQLite'): - self.query['createTourneysTable'] = """ """ + self.query['createTourneysTable'] = """CREATE TABLE TourneyTypes ( + id INTEGER PRIMARY KEY, + siteId INTEGER, + buyin INTEGER, + fee INTEGER, + knockout INTEGER, + rebuyOrAddon BOOL, + FOREIGN KEY(siteId) REFERENCES Sites(id) ON DELETE CASCADE)""" ################################ # Create HandsPlayers @@ -869,817 +873,19 @@ class FpdbSQLQueries: elif(self.dbname == 'SQLite'): self.query['addPlayersIndex'] = """ """ - ################################ - # Queries used in GuiGraphViewer - ################################ - - if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): - self.query['getPlayerId'] = """SELECT id from Players where name = %s""" + if(self.dbname == 'MySQL InnoDB' or self.dbname == 'PostgreSQL'): + self.query['set tx level'] = """SET SESSION TRANSACTION + ISOLATION LEVEL READ COMMITTED""" elif(self.dbname == 'SQLite'): - self.query['getPlayerId'] = """SELECT id from Players where name = %s""" + self.query['set tx level'] = """ """ + if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'): self.query['getSiteId'] = """SELECT id from Sites where name = %s""" elif(self.dbname == 'SQLite'): self.query['getSiteId'] = """SELECT id from Sites where name = %s""" - if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): - self.query['getRingProfitAllHandsPlayerIdSite'] = """ - SELECT hp.handId, hp.totalProfit, hp.totalProfit, hp.totalProfit - FROM HandsPlayers hp - INNER JOIN Players pl ON hp.playerId = pl.id - INNER JOIN Hands h ON h.id = hp.handId - INNER JOIN Gametypes g ON h.gametypeId = g.id - where pl.id in - AND pl.siteId in - AND h.handStart > '' - AND h.handStart < '' - AND g.bigBlind in - AND hp.tourneysPlayersId IS NULL - GROUP BY h.handStart, hp.handId, hp.totalProfit - ORDER BY h.handStart""" - - if self.dbname in ['MySQL InnoDB']: - self.query['playerDetailedStats'] = """ - select AS hgametypeid - ,gt.base - ,gt.category - ,upper(gt.limitType) AS limittype - ,s.name - ,min(gt.bigBlind) AS minbigblind - ,max(gt.bigBlind) AS maxbigblind - /*, AS gtid*/ - , AS plposition - ,count(1) AS n - ,100.0*sum(cast(hp.street0VPI as integer))/count(1) AS vpip - ,100.0*sum(cast(hp.street0Aggr as integer))/count(1) AS pfr - ,case when sum(cast(hp.street0_3Bchance as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street0_3Bdone as integer))/sum(cast(hp.street0_3Bchance as integer)) - end AS pf3 - ,case when sum(cast(hp.stealattemptchance as integer)) = 0 then -999 - else 100.0*sum(cast(hp.stealattempted as integer))/sum(cast(hp.stealattemptchance as integer)) - end AS steals - ,100.0*sum(cast(hp.street1Seen as integer))/count(1) AS saw_f - ,100.0*sum(cast(hp.sawShowdown as integer))/count(1) AS sawsd - ,case when sum(cast(hp.street1Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.sawShowdown as integer))/sum(cast(hp.street1Seen as integer)) - end AS wtsdwsf - ,case when sum(cast(hp.sawShowdown as integer)) = 0 then -999 - else 100.0*sum(cast(hp.wonAtSD as integer))/sum(cast(hp.sawShowdown as integer)) - end AS wmsd - ,case when sum(cast(hp.street1Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street1Aggr as integer))/sum(cast(hp.street1Seen as integer)) - end AS flafq - ,case when sum(cast(hp.street2Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street2Aggr as integer))/sum(cast(hp.street2Seen as integer)) - end AS tuafq - ,case when sum(cast(hp.street3Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street3Aggr as integer))/sum(cast(hp.street3Seen as integer)) - end AS rvafq - ,case when sum(cast(hp.street1Seen as integer))+sum(cast(hp.street2Seen as integer))+sum(cast(hp.street3Seen as integer)) = 0 then -999 - else 100.0*(sum(cast(hp.street1Aggr as integer))+sum(cast(hp.street2Aggr as integer))+sum(cast(hp.street3Aggr as integer))) - /(sum(cast(hp.street1Seen as integer))+sum(cast(hp.street2Seen as integer))+sum(cast(hp.street3Seen as integer))) - end AS pofafq - ,sum(hp.totalProfit)/100.0 AS net - ,sum(hp.rake)/100.0 AS rake - ,100.0*avg(hp.totalProfit/(gt.bigBlind+0.0)) AS bbper100 - ,avg(hp.totalProfit)/100.0 AS profitperhand - ,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr - ,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr - ,avg(h.seats+0.0) AS avgseats - ,variance(hp.totalProfit/100.0) AS variance - from HandsPlayers hp - inner join Hands h on (h.id = hp.handId) - inner join Gametypes gt on (gt.Id = h.gameTypeId) - inner join Sites s on (s.Id = gt.siteId) - where hp.playerId in - and hp.tourneysPlayersId IS NULL - and h.seats - - - and date_format(h.handStart, '%Y-%m-%d') - group by hgameTypeId - ,hp.playerId - ,gt.base - ,gt.category - - ,plposition - ,upper(gt.limitType) - ,s.name - order by hp.playerId - ,gt.base - ,gt.category - - ,case when 'B' then 'B' - when 'S' then 'S' - else concat('Z', ) - end - - ,maxbigblind desc - ,upper(gt.limitType) - ,s.name - """ - elif self.dbname in ['PostgreSQL']: - self.query['playerDetailedStats'] = """ - select AS hgametypeid - ,gt.base - ,gt.category - ,upper(gt.limitType) AS limittype - ,s.name - ,min(gt.bigBlind) AS minbigblind - ,max(gt.bigBlind) AS maxbigblind - /*, AS gtid*/ - , AS plposition - ,count(1) AS n - ,100.0*sum(cast(hp.street0VPI as integer))/count(1) AS vpip - ,100.0*sum(cast(hp.street0Aggr as integer))/count(1) AS pfr - ,case when sum(cast(hp.street0_3Bchance as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street0_3Bdone as integer))/sum(cast(hp.street0_3Bchance as integer)) - end AS pf3 - ,case when sum(cast(hp.stealattemptchance as integer)) = 0 then -999 - else 100.0*sum(cast(hp.stealattempted as integer))/sum(cast(hp.stealattemptchance as integer)) - end AS steals - ,100.0*sum(cast(hp.street1Seen as integer))/count(1) AS saw_f - ,100.0*sum(cast(hp.sawShowdown as integer))/count(1) AS sawsd - ,case when sum(cast(hp.street1Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.sawShowdown as integer))/sum(cast(hp.street1Seen as integer)) - end AS wtsdwsf - ,case when sum(cast(hp.sawShowdown as integer)) = 0 then -999 - else 100.0*sum(cast(hp.wonAtSD as integer))/sum(cast(hp.sawShowdown as integer)) - end AS wmsd - ,case when sum(cast(hp.street1Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street1Aggr as integer))/sum(cast(hp.street1Seen as integer)) - end AS flafq - ,case when sum(cast(hp.street2Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street2Aggr as integer))/sum(cast(hp.street2Seen as integer)) - end AS tuafq - ,case when sum(cast(hp.street3Seen as integer)) = 0 then -999 - else 100.0*sum(cast(hp.street3Aggr as integer))/sum(cast(hp.street3Seen as integer)) - end AS rvafq - ,case when sum(cast(hp.street1Seen as integer))+sum(cast(hp.street2Seen as integer))+sum(cast(hp.street3Seen as integer)) = 0 then -999 - else 100.0*(sum(cast(hp.street1Aggr as integer))+sum(cast(hp.street2Aggr as integer))+sum(cast(hp.street3Aggr as integer))) - /(sum(cast(hp.street1Seen as integer))+sum(cast(hp.street2Seen as integer))+sum(cast(hp.street3Seen as integer))) - end AS pofafq - ,sum(hp.totalProfit)/100.0 AS net - ,sum(hp.rake)/100.0 AS rake - ,100.0*avg(hp.totalProfit/(gt.bigBlind+0.0)) AS bbper100 - ,avg(hp.totalProfit)/100.0 AS profitperhand - ,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr - ,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr - ,avg(h.seats+0.0) AS avgseats - ,variance(hp.totalProfit/100.0) AS variance - from HandsPlayers hp - inner join Hands h on (h.id = hp.handId) - inner join Gametypes gt on (gt.Id = h.gameTypeId) - inner join Sites s on (s.Id = gt.siteId) - where hp.playerId in - and hp.tourneysPlayersId IS NULL - and h.seats - - - and to_char(h.handStart, 'YYYY-MM-DD') - group by hgameTypeId - ,hp.playerId - ,gt.base - ,gt.category - - ,plposition - ,upper(gt.limitType) - ,s.name - order by hp.playerId - ,gt.base - ,gt.category - - ,case when 'B' then 'B' - when 'S' then 'S' - else 'Z'|| - end - - ,maxbigblind desc - ,upper(gt.limitType) - ,s.name - """ - elif(self.dbname == 'SQLite'): - self.query['playerDetailedStats'] = """ """ - - if(self.dbname == 'MySQL InnoDB'): - self.query['playerStats'] = """ - SELECT - concat(upper(stats.limitType), ' ' - ,concat(upper(substring(stats.category,1,1)),substring(stats.category,2) ), ' ' - ,stats.name, ' ' - ,cast(stats.bigBlindDesc as char) - ) AS Game - ,stats.n - ,stats.vpip - ,stats.pfr - ,stats.pf3 - ,stats.steals - ,stats.saw_f - ,stats.sawsd - ,stats.wtsdwsf - ,stats.wmsd - ,stats.FlAFq - ,stats.TuAFq - ,stats.RvAFq - ,stats.PoFAFq - ,stats.Net - ,stats.BBper100 - ,stats.Profitperhand - ,case when hprof2.variance = -999 then '-' - else format(hprof2.variance, 2) - end AS Variance - ,stats.AvgSeats - FROM - (select /* stats from hudcache */ - gt.base - ,gt.category - ,upper(gt.limitType) as limitType - ,s.name - , AS bigBlindDesc - , AS gtId - ,sum(HDs) AS n - ,format(100.0*sum(street0VPI)/sum(HDs),1) AS vpip - ,format(100.0*sum(street0Aggr)/sum(HDs),1) AS pfr - ,case when sum(street0_3Bchance) = 0 then '0' - else format(100.0*sum(street0_3Bdone)/sum(street0_3Bchance),1) - end AS pf3 - ,case when sum(stealattemptchance) = 0 then '-' - else format(100.0*sum(stealattempted)/sum(stealattemptchance),1) - end AS steals - ,format(100.0*sum(street1Seen)/sum(HDs),1) AS saw_f - ,format(100.0*sum(sawShowdown)/sum(HDs),1) AS sawsd - ,case when sum(street1Seen) = 0 then '-' - else format(100.0*sum(sawShowdown)/sum(street1Seen),1) - end AS wtsdwsf - ,case when sum(sawShowdown) = 0 then '-' - else format(100.0*sum(wonAtSD)/sum(sawShowdown),1) - end AS wmsd - ,case when sum(street1Seen) = 0 then '-' - else format(100.0*sum(street1Aggr)/sum(street1Seen),1) - end AS FlAFq - ,case when sum(street2Seen) = 0 then '-' - else format(100.0*sum(street2Aggr)/sum(street2Seen),1) - end AS TuAFq - ,case when sum(street3Seen) = 0 then '-' - else format(100.0*sum(street3Aggr)/sum(street3Seen),1) - end AS RvAFq - ,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then '-' - else format(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr)) - /(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),1) - end AS PoFAFq - ,format(sum(totalProfit)/100.0,2) AS Net - ,format((sum(totalProfit/(gt.bigBlind+0.0))) / (sum(HDs)/100.0),2) - AS BBper100 - ,format( (sum(totalProfit)/100.0) / sum(HDs), 4) AS Profitperhand - ,format( sum(activeSeats*HDs)/(sum(HDs)+0.0), 2) AS AvgSeats - from Gametypes gt - inner join Sites s on s.Id = gt.siteId - inner join HudCache hc on hc.gameTypeId = gt.Id - where hc.playerId in - and - and hc.activeSeats - and concat( '20', substring(hc.styleKey,2,2), '-', substring(hc.styleKey,4,2), '-' - , substring(hc.styleKey,6,2) ) - group by gt.base - ,gt.category - ,upper(gt.limitType) - ,s.name - - ,gtId - ) stats - inner join - ( select # profit from handsplayers/handsactions - hprof.gtId, sum(hprof.profit) sum_profit, - avg(hprof.profit/100.0) profitperhand, - case when hprof.gtId = -1 then -999 - else variance(hprof.profit/100.0) - end as variance - from - (select hp.handId, as gtId, hp.totalProfit as profit - from HandsPlayers hp - inner join Hands h ON h.id = hp.handId - where hp.playerId in - and hp.tourneysPlayersId IS NULL - and date_format(h.handStart, '%Y-%m-%d') - group by hp.handId, gtId, hp.totalProfit - ) hprof - group by hprof.gtId - ) hprof2 - on hprof2.gtId = stats.gtId - order by stats.category, stats.limittype, stats.bigBlindDesc desc """ - elif(self.dbname == 'PostgreSQL'): - self.query['playerStats'] = """ - SELECT upper(stats.limitType) || ' ' - || initcap(stats.category) || ' ' - || stats.name || ' ' - || stats.bigBlindDesc AS Game - ,stats.n - ,stats.vpip - ,stats.pfr - ,stats.pf3 - ,stats.steals - ,stats.saw_f - ,stats.sawsd - ,stats.wtsdwsf - ,stats.wmsd - ,stats.FlAFq - ,stats.TuAFq - ,stats.RvAFq - ,stats.PoFAFq - ,stats.Net - ,stats.BBper100 - ,stats.Profitperhand - ,case when hprof2.variance = -999 then '-' - else to_char(hprof2.variance, '0D00') - end AS Variance - ,AvgSeats - FROM - (select gt.base - ,gt.category - ,upper(gt.limitType) AS limitType - ,s.name - , AS bigBlindDesc - , AS gtId - ,sum(HDs) as n - ,to_char(100.0*sum(street0VPI)/sum(HDs),'990D0') AS vpip - ,to_char(100.0*sum(street0Aggr)/sum(HDs),'90D0') AS pfr - ,case when sum(street0_3Bchance) = 0 then '0' - else to_char(100.0*sum(street0_3Bdone)/sum(street0_3Bchance),'90D0') - end AS pf3 - ,case when sum(stealattemptchance) = 0 then '-' - else to_char(100.0*sum(stealattempted)/sum(stealattemptchance),'90D0') - end AS steals - ,to_char(100.0*sum(street1Seen)/sum(HDs),'90D0') AS saw_f - ,to_char(100.0*sum(sawShowdown)/sum(HDs),'90D0') AS sawsd - ,case when sum(street1Seen) = 0 then '-' - else to_char(100.0*sum(sawShowdown)/sum(street1Seen),'90D0') - end AS wtsdwsf - ,case when sum(sawShowdown) = 0 then '-' - else to_char(100.0*sum(wonAtSD)/sum(sawShowdown),'90D0') - end AS wmsd - ,case when sum(street1Seen) = 0 then '-' - else to_char(100.0*sum(street1Aggr)/sum(street1Seen),'90D0') - end AS FlAFq - ,case when sum(street2Seen) = 0 then '-' - else to_char(100.0*sum(street2Aggr)/sum(street2Seen),'90D0') - end AS TuAFq - ,case when sum(street3Seen) = 0 then '-' - else to_char(100.0*sum(street3Aggr)/sum(street3Seen),'90D0') - end AS RvAFq - ,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then '-' - else to_char(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr)) - /(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),'90D0') - end AS PoFAFq - ,round(sum(totalProfit)/100.0,2) AS Net - ,to_char((sum(totalProfit/(gt.bigBlind+0.0))) / (sum(HDs)/100.0), '990D00') - AS BBper100 - ,to_char(sum(totalProfit/100.0) / (sum(HDs)+0.0), '990D0000') AS Profitperhand - ,to_char(sum(activeSeats*HDs)/(sum(HDs)+0.0),'90D00') AS AvgSeats - from Gametypes gt - inner join Sites s on s.Id = gt.siteId - inner join HudCache hc on hc.gameTypeId = gt.Id - where hc.playerId in - and - and hc.activeSeats - and '20' || SUBSTR(hc.styleKey,2,2) || '-' || SUBSTR(hc.styleKey,4,2) || '-' - || SUBSTR(hc.styleKey,6,2) - group by gt.base - ,gt.category - ,upper(gt.limitType) - ,s.name - - ,gtId - ) stats - inner join - ( select - hprof.gtId, sum(hprof.profit) AS sum_profit, - avg(hprof.profit/100.0) AS profitperhand, - case when hprof.gtId = -1 then -999 - else variance(hprof.profit/100.0) - end as variance - from - (select hp.handId, as gtId, hp.totalProfit as profit - from HandsPlayers hp - inner join Hands h ON (h.id = hp.handId) - where hp.playerId in - and hp.tourneysPlayersId IS NULL - and to_char(h.handStart, 'YYYY-MM-DD') - group by hp.handId, gtId, hp.totalProfit - ) hprof - group by hprof.gtId - ) hprof2 - on hprof2.gtId = stats.gtId - order by stats.base, stats.limittype, stats.bigBlindDesc desc """ - elif(self.dbname == 'SQLite'): - self.query['playerStats'] = """ """ - - if(self.dbname == 'MySQL InnoDB'): - self.query['playerStatsByPosition'] = """ - SELECT - concat(upper(stats.limitType), ' ' - ,concat(upper(substring(stats.category,1,1)),substring(stats.category,2) ), ' ' - ,stats.name, ' ' - ,cast(stats.bigBlindDesc as char) - ) AS Game - ,case when stats.PlPosition = -2 then 'BB' - when stats.PlPosition = -1 then 'SB' - when stats.PlPosition = 0 then 'Btn' - when stats.PlPosition = 1 then 'CO' - when stats.PlPosition = 2 then 'MP' - when stats.PlPosition = 5 then 'EP' - else '??' - end AS PlPosition - ,stats.n - ,stats.vpip - ,stats.pfr - ,stats.pf3 - ,stats.steals - ,stats.saw_f - ,stats.sawsd - ,stats.wtsdwsf - ,stats.wmsd - ,stats.FlAFq - ,stats.TuAFq - ,stats.RvAFq - ,stats.PoFAFq - ,stats.Net - ,stats.BBper100 - ,stats.Profitperhand - ,case when hprof2.variance = -999 then '-' - else format(hprof2.variance, 2) - end AS Variance - ,stats.AvgSeats - FROM - (select /* stats from hudcache */ - gt.base - ,gt.category - ,upper(gt.limitType) AS limitType - ,s.name - , AS bigBlindDesc - , AS gtId - ,case when hc.position = 'B' then -2 - when hc.position = 'S' then -1 - when hc.position = 'D' then 0 - when hc.position = 'C' then 1 - when hc.position = 'M' then 2 - when hc.position = 'E' then 5 - else 9 - end as PlPosition - ,sum(HDs) AS n - ,format(100.0*sum(street0VPI)/sum(HDs),1) AS vpip - ,format(100.0*sum(street0Aggr)/sum(HDs),1) AS pfr - ,case when sum(street0_3Bchance) = 0 then '0' - else format(100.0*sum(street0_3Bdone)/sum(street0_3Bchance),1) - end AS pf3 - ,case when sum(stealattemptchance) = 0 then '-' - else format(100.0*sum(stealattempted)/sum(stealattemptchance),1) - end AS steals - ,format(100.0*sum(street1Seen)/sum(HDs),1) AS saw_f - ,format(100.0*sum(sawShowdown)/sum(HDs),1) AS sawsd - ,case when sum(street1Seen) = 0 then '-' - else format(100.0*sum(sawShowdown)/sum(street1Seen),1) - end AS wtsdwsf - ,case when sum(sawShowdown) = 0 then '-' - else format(100.0*sum(wonAtSD)/sum(sawShowdown),1) - end AS wmsd - ,case when sum(street1Seen) = 0 then '-' - else format(100.0*sum(street1Aggr)/sum(street1Seen),1) - end AS FlAFq - ,case when sum(street2Seen) = 0 then '-' - else format(100.0*sum(street2Aggr)/sum(street2Seen),1) - end AS TuAFq - ,case when sum(street3Seen) = 0 then '-' - else format(100.0*sum(street3Aggr)/sum(street3Seen),1) - end AS RvAFq - ,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then '-' - else format(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr)) - /(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),1) - end AS PoFAFq - ,format(sum(totalProfit)/100.0,2) AS Net - ,format((sum(totalProfit/(gt.bigBlind+0.0))) / (sum(HDs)/100.0),2) - AS BBper100 - ,format( (sum(totalProfit)/100.0) / sum(HDs), 4) AS Profitperhand - ,format( sum(activeSeats*HDs)/(sum(HDs)+0.0), 2) AS AvgSeats - from Gametypes gt - inner join Sites s on s.Id = gt.siteId - inner join HudCache hc on hc.gameTypeId = gt.Id - where hc.playerId in - and - and hc.activeSeats - and concat( '20', substring(hc.styleKey,2,2), '-', substring(hc.styleKey,4,2), '-' - , substring(hc.styleKey,6,2) ) - group by gt.base - ,gt.category - ,upper(gt.limitType) - ,s.name - - ,gtId - - ,PlPosition - ) stats - inner join - ( select # profit from handsplayers/handsactions - hprof.gtId, - case when hprof.position = 'B' then -2 - when hprof.position = 'S' then -1 - when hprof.position in ('3','4') then 2 - when hprof.position in ('6','7') then 5 - else hprof.position - end as PlPosition, - sum(hprof.profit) as sum_profit, - avg(hprof.profit/100.0) as profitperhand, - case when hprof.gtId = -1 then -999 - else variance(hprof.profit/100.0) - end as variance - from - (select hp.handId, as gtId, hp.position - , hp.totalProfit as profit - from HandsPlayers hp - inner join Hands h ON (h.id = hp.handId) - where hp.playerId in - and hp.tourneysPlayersId IS NULL - and date_format(h.handStart, '%Y-%m-%d') - group by hp.handId, gtId, hp.position, hp.totalProfit - ) hprof - group by hprof.gtId, PlPosition - ) hprof2 - on ( hprof2.gtId = stats.gtId - and hprof2.PlPosition = stats.PlPosition) - order by stats.category, stats.limitType, stats.bigBlindDesc desc - , cast(stats.PlPosition as signed) - """ - elif(self.dbname == 'PostgreSQL'): - self.query['playerStatsByPosition'] = """ - select /* stats from hudcache */ - upper(stats.limitType) || ' ' - || upper(substr(stats.category,1,1)) || substr(stats.category,2) || ' ' - || stats.name || ' ' - || stats.bigBlindDesc AS Game - ,case when stats.PlPosition = -2 then 'BB' - when stats.PlPosition = -1 then 'SB' - when stats.PlPosition = 0 then 'Btn' - when stats.PlPosition = 1 then 'CO' - when stats.PlPosition = 2 then 'MP' - when stats.PlPosition = 5 then 'EP' - else '??' - end AS PlPosition - ,stats.n - ,stats.vpip - ,stats.pfr - ,stats.pf3 - ,stats.steals - ,stats.saw_f - ,stats.sawsd - ,stats.wtsdwsf - ,stats.wmsd - ,stats.FlAFq - ,stats.TuAFq - ,stats.RvAFq - ,stats.PoFAFq - ,stats.Net - ,stats.BBper100 - ,stats.Profitperhand - ,case when hprof2.variance = -999 then '-' - else to_char(hprof2.variance, '0D00') - end AS Variance - ,stats.AvgSeats - FROM - (select /* stats from hudcache */ - gt.base - ,gt.category - ,upper(gt.limitType) AS limitType - ,s.name - , AS bigBlindDesc - , AS gtId - ,case when hc.position = 'B' then -2 - when hc.position = 'S' then -1 - when hc.position = 'D' then 0 - when hc.position = 'C' then 1 - when hc.position = 'M' then 2 - when hc.position = 'E' then 5 - else 9 - end AS PlPosition - ,sum(HDs) AS n - ,to_char(round(100.0*sum(street0VPI)/sum(HDs)),'990D0') AS vpip - ,to_char(round(100.0*sum(street0Aggr)/sum(HDs)),'90D0') AS pfr - ,case when sum(street0_3Bchance) = 0 then '0' - else to_char(100.0*sum(street0_3Bdone)/sum(street0_3Bchance),'90D0') - end AS pf3 - ,case when sum(stealattemptchance) = 0 then '-' - else to_char(100.0*sum(stealattempted)/sum(stealattemptchance),'90D0') - end AS steals - ,to_char(round(100.0*sum(street1Seen)/sum(HDs)),'90D0') AS saw_f - ,to_char(round(100.0*sum(sawShowdown)/sum(HDs)),'90D0') AS sawsd - ,case when sum(street1Seen) = 0 then '-' - else to_char(round(100.0*sum(sawShowdown)/sum(street1Seen)),'90D0') - end AS wtsdwsf - ,case when sum(sawShowdown) = 0 then '-' - else to_char(round(100.0*sum(wonAtSD)/sum(sawShowdown)),'90D0') - end AS wmsd - ,case when sum(street1Seen) = 0 then '-' - else to_char(round(100.0*sum(street1Aggr)/sum(street1Seen)),'90D0') - end AS FlAFq - ,case when sum(street2Seen) = 0 then '-' - else to_char(round(100.0*sum(street2Aggr)/sum(street2Seen)),'90D0') - end AS TuAFq - ,case when sum(street3Seen) = 0 then '-' - else to_char(round(100.0*sum(street3Aggr)/sum(street3Seen)),'90D0') - end AS RvAFq - ,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then '-' - else to_char(round(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr)) - /(sum(street1Seen)+sum(street2Seen)+sum(street3Seen))),'90D0') - end AS PoFAFq - ,to_char(sum(totalProfit)/100.0,'9G999G990D00') AS Net - ,case when sum(HDs) = 0 then '0' - else to_char(sum(totalProfit/(gt.bigBlind+0.0)) / (sum(HDs)/100.0), '990D00') - end AS BBper100 - ,case when sum(HDs) = 0 then '0' - else to_char( (sum(totalProfit)/100.0) / sum(HDs), '90D0000') - end AS Profitperhand - ,to_char(sum(activeSeats*HDs)/(sum(HDs)+0.0),'90D00') AS AvgSeats - from Gametypes gt - inner join Sites s on (s.Id = gt.siteId) - inner join HudCache hc on (hc.gameTypeId = gt.Id) - where hc.playerId in - and - and hc.activeSeats - and '20' || SUBSTR(hc.styleKey,2,2) || '-' || SUBSTR(hc.styleKey,4,2) || '-' - || SUBSTR(hc.styleKey,6,2) - group by gt.base - ,gt.category - ,upper(gt.limitType) - ,s.name - - ,gtId - - ,PlPosition - ) stats - inner join - ( select /* profit from handsplayers/handsactions */ - hprof.gtId, - case when hprof.position = 'B' then -2 - when hprof.position = 'S' then -1 - when hprof.position in ('3','4') then 2 - when hprof.position in ('6','7') then 5 - else cast(hprof.position as smallint) - end as PlPosition, - sum(hprof.profit) as sum_profit, - avg(hprof.profit/100.0) as profitperhand, - case when hprof.gtId = -1 then -999 - else variance(hprof.profit/100.0) - end as variance - from - (select hp.handId, as gtId, hp.position - , hp.totalProfit as profit - from HandsPlayers hp - inner join Hands h ON (h.id = hp.handId) - where hp.playerId in - and hp.tourneysPlayersId IS NULL - and to_char(h.handStart, 'YYYY-MM-DD') - group by hp.handId, gameTypeId, hp.position, hp.totalProfit - ) hprof - group by hprof.gtId, PlPosition - ) hprof2 - on ( hprof2.gtId = stats.gtId - and hprof2.PlPosition = stats.PlPosition) - order by stats.category, stats.limitType, stats.bigBlindDesc desc - , cast(stats.PlPosition as smallint) - """ - elif(self.dbname == 'SQLite'): - self.query['playerStatsByPosition'] = """ """ - - if(self.dbname == 'MySQL InnoDB'): - self.query['playerStatsByPositionAndHoleCards'] = """ - SELECT - concat(upper(stats.limitType), ' ' - ,concat(upper(substring(stats.category,1,1)),substring(stats.category,2) ), ' ' - ,stats.name, ' $' - ,cast(trim(leading ' ' from - case when stats.bigBlind < 100 then format(stats.bigBlind/100.0,2) - else format(stats.bigBlind/100.0,0) - end ) as char) - ) AS Game - ,case when stats.PlPosition = -2 then 'BB' - when stats.PlPosition = -1 then 'SB' - when stats.PlPosition = 0 then 'Btn' - when stats.PlPosition = 1 then 'CO' - when stats.PlPosition = 2 then 'MP' - when stats.PlPosition = 5 then 'EP' - else '??' - end AS PlPosition - /*,stats.n*/,hprof2.n - /*,stats.vpip*/,0 - /*,stats.pfr*/,0 - /*,stats.saw_f*/,0 - /*,stats.sawsd*/,0 - /*,stats.wtsdwsf*/,0 - /*,stats.wmsd*/,0 - /*,stats.FlAFq*/,0 - /*,stats.TuAFq*/,0 - /*,stats.RvAFq*/,0 - /*,stats.PoFAFq*/,0 - /* if you have handsactions data the next 3 fields should give same answer as - following 3 commented out fields */ - /*,stats.Net - ,stats.BBper100 - ,stats.Profitperhand*/ - ,format(hprof2.sum_profit/100.0,2) AS Net - /*,format((hprof2.sum_profit/(stats.bigBlind+0.0)) / (stats.n/100.0),2)*/,0 - AS BBlPer100 - ,hprof2.profitperhand AS Profitperhand - ,format(hprof2.variance,2) AS Variance - FROM - (select /* stats from hudcache */ - gt.base - ,gt.category - ,upper(gt.limitType) as limitType - ,s.name - ,gt.bigBlind - ,hc.gametypeId - ,case when hc.position = 'B' then -2 - when hc.position = 'S' then -1 - when hc.position = 'D' then 0 - when hc.position = 'C' then 1 - when hc.position = 'M' then 2 - when hc.position = 'E' then 5 - else 9 - end as PlPosition - ,sum(HDs) AS n - ,format(100.0*sum(street0VPI)/sum(HDs),1) AS vpip - ,format(100.0*sum(street0Aggr)/sum(HDs),1) AS pfr - ,format(100.0*sum(street1Seen)/sum(HDs),1) AS saw_f - ,format(100.0*sum(sawShowdown)/sum(HDs),1) AS sawsd - ,case when sum(street1Seen) = 0 then '-' - else format(100.0*sum(sawShowdown)/sum(street1Seen),1) - end AS wtsdwsf - ,case when sum(sawShowdown) = 0 then '-' - end AS wtsdwsf - ,case when sum(sawShowdown) = 0 then '-' - else format(100.0*sum(wonAtSD)/sum(sawShowdown),1) - end AS wmsd - ,case when sum(street1Seen) = 0 then '-' - else format(100.0*sum(street1Aggr)/sum(street1Seen),1) - end AS FlAFq - ,case when sum(street2Seen) = 0 then '-' - else format(100.0*sum(street2Aggr)/sum(street2Seen),1) - end AS TuAFq - ,case when sum(street3Seen) = 0 then '-' - else format(100.0*sum(street3Aggr)/sum(street3Seen),1) - end AS RvAFq - ,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then '-' - else format(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr)) - /(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),1) - end AS PoFAFq - ,format(sum(totalProfit)/100.0,2) AS Net - ,format((sum(totalProfit)/(gt.bigBlind+0.0)) / (sum(HDs)/100.0),2) - AS BBper100 - ,format( (sum(totalProfit)/100.0) / sum(HDs), 4) AS Profitperhand - from Gametypes gt - inner join Sites s on s.Id = gt.siteId - inner join HudCache hc on hc.gameTypeId = gt.Id - where hc.playerId in - # use here ? - group by gt.base - ,gt.category - ,upper(gt.limitType) - ,s.name - ,gt.bigBlind - ,hc.gametypeId - ,PlPosition - ) stats - inner join - ( select # profit from handsplayers/handsactions - hprof.gameTypeId, - case when hprof.position = 'B' then -2 - when hprof.position = 'S' then -1 - when hprof.position in ('3','4') then 2 - when hprof.position in ('6','7') then 5 - else hprof.position - end as PlPosition, - sum(hprof.profit) as sum_profit, - avg(hprof.profit/100.0) as profitperhand, - variance(hprof.profit/100.0) as variance, - count(*) as n - from - (select hp.handId, h.gameTypeId, hp.position, hp.winnings, SUM(ha.amount) as costs - , hp.winnings - SUM(ha.amount) as profit - from HandsPlayers hp - inner join Hands h ON h.id = hp.handId - left join HandsActions ha ON ha.handsPlayerId = hp.id - where hp.playerId in - # use here ? - and hp.tourneysPlayersId IS NULL - and ((hp.card1Value = and hp.card2Value = ) or (hp.card1Value = and hp.card2Value = )) - group by hp.handId, h.gameTypeId, hp.position, hp.winnings - ) hprof - group by hprof.gameTypeId, PlPosition - ) hprof2 - on ( hprof2.gameTypeId = stats.gameTypeId - and hprof2.PlPosition = stats.PlPosition) - order by stats.category, stats.limittype, stats.bigBlind, cast(stats.PlPosition as signed) - """ if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): self.query['getGames'] = """SELECT DISTINCT category from Gametypes""" @@ -1687,311 +893,6 @@ class FpdbSQLQueries: self.query['getLimits'] = """SELECT DISTINCT bigBlind from Gametypes ORDER by bigBlind DESC""" - #################################### - # Queries to rebuild/modify hudcache - #################################### - - if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): - self.query['clearHudCache'] = """DELETE FROM HudCache""" - - if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): - self.query['rebuildHudCache'] = """ - INSERT INTO HudCache - (gametypeId - ,playerId - ,activeSeats - ,position - ,tourneyTypeId - ,styleKey - ,HDs - ,wonWhenSeenStreet1 - ,wonAtSD - ,street0VPI - ,street0Aggr - ,street0_3BChance - ,street0_3BDone - ,street1Seen - ,street2Seen - ,street3Seen - ,street4Seen - ,sawShowdown - ,street1Aggr - ,street2Aggr - ,street3Aggr - ,street4Aggr - ,otherRaisedStreet1 - ,otherRaisedStreet2 - ,otherRaisedStreet3 - ,otherRaisedStreet4 - ,foldToOtherRaisedStreet1 - ,foldToOtherRaisedStreet2 - ,foldToOtherRaisedStreet3 - ,foldToOtherRaisedStreet4 - ,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 - ) - SELECT h.gametypeId - ,hp.playerId - ,h.seats - ,case when hp.position = 'B' then 'B' - when hp.position = 'S' then 'S' - when hp.position = '0' then 'D' - when hp.position = '1' then 'C' - when hp.position = '2' then 'M' - when hp.position = '3' then 'M' - when hp.position = '4' then 'M' - when hp.position = '5' then 'E' - when hp.position = '6' then 'E' - when hp.position = '7' then 'E' - when hp.position = '8' then 'E' - when hp.position = '9' then 'E' - else 'E' - end AS hc_position - ,hp.tourneyTypeId - ,date_format(h.handStart, 'd%y%m%d') - ,count(1) - ,sum(wonWhenSeenStreet1) - ,sum(wonAtSD) - ,sum(CAST(street0VPI as integer)) - ,sum(CAST(street0Aggr as integer)) - ,sum(CAST(street0_3BChance as integer)) - ,sum(CAST(street0_3BDone as integer)) - ,sum(CAST(street1Seen as integer)) - ,sum(CAST(street2Seen as integer)) - ,sum(CAST(street3Seen as integer)) - ,sum(CAST(street4Seen as integer)) - ,sum(CAST(sawShowdown as integer)) - ,sum(CAST(street1Aggr as integer)) - ,sum(CAST(street2Aggr as integer)) - ,sum(CAST(street3Aggr as integer)) - ,sum(CAST(street4Aggr as integer)) - ,sum(CAST(otherRaisedStreet1 as integer)) - ,sum(CAST(otherRaisedStreet2 as integer)) - ,sum(CAST(otherRaisedStreet3 as integer)) - ,sum(CAST(otherRaisedStreet4 as integer)) - ,sum(CAST(foldToOtherRaisedStreet1 as integer)) - ,sum(CAST(foldToOtherRaisedStreet2 as integer)) - ,sum(CAST(foldToOtherRaisedStreet3 as integer)) - ,sum(CAST(foldToOtherRaisedStreet4 as integer)) - ,sum(CAST(stealAttemptChance as integer)) - ,sum(CAST(stealAttempted as integer)) - ,sum(CAST(foldBbToStealChance as integer)) - ,sum(CAST(foldedBbToSteal as integer)) - ,sum(CAST(foldSbToStealChance as integer)) - ,sum(CAST(foldedSbToSteal as integer)) - ,sum(CAST(street1CBChance as integer)) - ,sum(CAST(street1CBDone as integer)) - ,sum(CAST(street2CBChance as integer)) - ,sum(CAST(street2CBDone as integer)) - ,sum(CAST(street3CBChance as integer)) - ,sum(CAST(street3CBDone as integer)) - ,sum(CAST(street4CBChance as integer)) - ,sum(CAST(street4CBDone as integer)) - ,sum(CAST(foldToStreet1CBChance as integer)) - ,sum(CAST(foldToStreet1CBDone as integer)) - ,sum(CAST(foldToStreet2CBChance as integer)) - ,sum(CAST(foldToStreet2CBDone as integer)) - ,sum(CAST(foldToStreet3CBChance as integer)) - ,sum(CAST(foldToStreet3CBDone as integer)) - ,sum(CAST(foldToStreet4CBChance as integer)) - ,sum(CAST(foldToStreet4CBDone as integer)) - ,sum(CAST(totalProfit as integer)) - ,sum(CAST(street1CheckCallRaiseChance as integer)) - ,sum(CAST(street1CheckCallRaiseDone as integer)) - ,sum(CAST(street2CheckCallRaiseChance as integer)) - ,sum(CAST(street2CheckCallRaiseDone as integer)) - ,sum(CAST(street3CheckCallRaiseChance as integer)) - ,sum(CAST(street3CheckCallRaiseDone as integer)) - ,sum(CAST(street4CheckCallRaiseChance as integer)) - ,sum(CAST(street4CheckCallRaiseDone as integer)) - FROM HandsPlayers hp - INNER JOIN Hands h ON (h.id = hp.handId) - GROUP BY h.gametypeId - ,hp.playerId - ,h.seats - ,hc_position - ,hp.tourneyTypeId - ,date_format(h.handStart, 'd%y%m%d') -""" - elif (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'): - self.query['rebuildHudCache'] = """ - INSERT INTO HudCache - (gametypeId - ,playerId - ,activeSeats - ,position - ,tourneyTypeId - ,styleKey - ,HDs - ,wonWhenSeenStreet1 - ,wonAtSD - ,street0VPI - ,street0Aggr - ,street0_3BChance - ,street0_3BDone - ,street1Seen - ,street2Seen - ,street3Seen - ,street4Seen - ,sawShowdown - ,street1Aggr - ,street2Aggr - ,street3Aggr - ,street4Aggr - ,otherRaisedStreet1 - ,otherRaisedStreet2 - ,otherRaisedStreet3 - ,otherRaisedStreet4 - ,foldToOtherRaisedStreet1 - ,foldToOtherRaisedStreet2 - ,foldToOtherRaisedStreet3 - ,foldToOtherRaisedStreet4 - ,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 - ) - SELECT h.gametypeId - ,hp.playerId - ,h.seats - ,case when hp.position = 'B' then 'B' - when hp.position = 'S' then 'S' - when hp.position = '0' then 'D' - when hp.position = '1' then 'C' - when hp.position = '2' then 'M' - when hp.position = '3' then 'M' - when hp.position = '4' then 'M' - when hp.position = '5' then 'E' - when hp.position = '6' then 'E' - when hp.position = '7' then 'E' - when hp.position = '8' then 'E' - when hp.position = '9' then 'E' - else 'E' - end AS hc_position - ,hp.tourneyTypeId - ,'d' || to_char(h.handStart, 'YYMMDD') - ,count(1) - ,sum(wonWhenSeenStreet1) - ,sum(wonAtSD) - ,sum(CAST(street0VPI as integer)) - ,sum(CAST(street0Aggr as integer)) - ,sum(CAST(street0_3BChance as integer)) - ,sum(CAST(street0_3BDone as integer)) - ,sum(CAST(street1Seen as integer)) - ,sum(CAST(street2Seen as integer)) - ,sum(CAST(street3Seen as integer)) - ,sum(CAST(street4Seen as integer)) - ,sum(CAST(sawShowdown as integer)) - ,sum(CAST(street1Aggr as integer)) - ,sum(CAST(street2Aggr as integer)) - ,sum(CAST(street3Aggr as integer)) - ,sum(CAST(street4Aggr as integer)) - ,sum(CAST(otherRaisedStreet1 as integer)) - ,sum(CAST(otherRaisedStreet2 as integer)) - ,sum(CAST(otherRaisedStreet3 as integer)) - ,sum(CAST(otherRaisedStreet4 as integer)) - ,sum(CAST(foldToOtherRaisedStreet1 as integer)) - ,sum(CAST(foldToOtherRaisedStreet2 as integer)) - ,sum(CAST(foldToOtherRaisedStreet3 as integer)) - ,sum(CAST(foldToOtherRaisedStreet4 as integer)) - ,sum(CAST(stealAttemptChance as integer)) - ,sum(CAST(stealAttempted as integer)) - ,sum(CAST(foldBbToStealChance as integer)) - ,sum(CAST(foldedBbToSteal as integer)) - ,sum(CAST(foldSbToStealChance as integer)) - ,sum(CAST(foldedSbToSteal as integer)) - ,sum(CAST(street1CBChance as integer)) - ,sum(CAST(street1CBDone as integer)) - ,sum(CAST(street2CBChance as integer)) - ,sum(CAST(street2CBDone as integer)) - ,sum(CAST(street3CBChance as integer)) - ,sum(CAST(street3CBDone as integer)) - ,sum(CAST(street4CBChance as integer)) - ,sum(CAST(street4CBDone as integer)) - ,sum(CAST(foldToStreet1CBChance as integer)) - ,sum(CAST(foldToStreet1CBDone as integer)) - ,sum(CAST(foldToStreet2CBChance as integer)) - ,sum(CAST(foldToStreet2CBDone as integer)) - ,sum(CAST(foldToStreet3CBChance as integer)) - ,sum(CAST(foldToStreet3CBDone as integer)) - ,sum(CAST(foldToStreet4CBChance as integer)) - ,sum(CAST(foldToStreet4CBDone as integer)) - ,sum(CAST(totalProfit as integer)) - ,sum(CAST(street1CheckCallRaiseChance as integer)) - ,sum(CAST(street1CheckCallRaiseDone as integer)) - ,sum(CAST(street2CheckCallRaiseChance as integer)) - ,sum(CAST(street2CheckCallRaiseDone as integer)) - ,sum(CAST(street3CheckCallRaiseChance as integer)) - ,sum(CAST(street3CheckCallRaiseDone as integer)) - ,sum(CAST(street4CheckCallRaiseChance as integer)) - ,sum(CAST(street4CheckCallRaiseDone as integer)) - FROM HandsPlayers hp - INNER JOIN Hands h ON (h.id = hp.handId) - GROUP BY h.gametypeId - ,hp.playerId - ,h.seats - ,hc_position - ,hp.tourneyTypeId - ,to_char(h.handStart, 'YYMMDD') -""" - - if __name__== "__main__": from optparse import OptionParser diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index f7b834a6..4fb0e8b3 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -23,25 +23,49 @@ import logging from HandHistoryConverter import * # Fulltilt HH Format converter +# TODO: cat tourno and table to make table name for tournaments class Fulltilt(HandHistoryConverter): # Static regexes - re_GameInfo = re.compile('- (?P\$|)?(?P[.0-9]+)/\$?(?P[.0-9]+) (Ante \$(?P[.0-9]+) )?- (?P(No Limit|Pot Limit|Limit))? (?P(Hold\'em|Omaha Hi|Razz))') + re_GameInfo = re.compile('''(?:(?P.+)\s\((?P\d+)\),\s)? + .+ + -\s(?P\$|)? + (?P[.0-9]+)/ + \$?(?P[.0-9]+)\s + (Ante\s\$?(?P[.0-9]+)\s)?-\s + (?P(No\sLimit|Pot\sLimit|Limit))?\s + (?P(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_TailSplitHands = re.compile(r"(\n\n+)") - re_HandInfo = re.compile('.*#(?P[0-9]+): Table (?P[- a-zA-Z]+) (\((?P.+)\) )?- \$?(?P[.0-9]+)/\$?(?P[.0-9]+) (Ante \$(?P[.0-9]+) )?- (?P[a-zA-Z\' ]+) - (?P.*)') + re_HandInfo = re.compile('''.*\#(?P[0-9]+):\s + (?:(?P.+)\s\((?P\d+)\),\s)? + Table\s + (?PPlay\sChip\s|PC)? + (?P
[-\s\da-zA-Z]+)\s + (\((?P.+)\)\s)?-\s + \$?(?P[.0-9]+)/\$?(?P[.0-9]+)\s(Ante\s\$?(?P[.0-9]+)\s)?-\s + (?P[a-zA-Z\/\'\s]+)\s-\s + (?P.*) + ''', re.VERBOSE) re_Button = re.compile('^The button is in seat #(?P
[- a-zA-Z]+)\'(?P.+?$)?", re.MULTILINE) + 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]+) - \$?(?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
[ 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
[- a-zA-Z]+)\'(?P.+?$)?", re.MULTILINE) + re_Button = re.compile('\n\n\n + re_PlayerInfo = re.compile('^', re.MULTILINE) + re_Card = re.compile('^', re.MULTILINE) + re_BoardLast = re.compile('^', 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" + "|".join(map(re.escape, players)) + ")" + logging.debug("player_re: " + player_re) + # + + self.re_PostSB = re.compile(r'^' % player_re, re.MULTILINE) + self.re_PostBB = re.compile(r'^' % player_re, re.MULTILINE) + self.re_Antes = re.compile(r"^%s: posts the ante \$?(?P[.0-9]+)" % player_re, re.MULTILINE) + self.re_BringIn = re.compile(r"^%s: brings[- ]in( low|) for \$?(?P[.0-9]+)" % player_re, re.MULTILINE) + self.re_PostBoth = re.compile(r'^' % player_re, re.MULTILINE) + + #r'\n\n' + self.re_HeroCards = re.compile(r'\n(?P\n)' % player_re, re.MULTILINE) + + #'^' + self.re_Action = re.compile(r'^' % player_re, re.MULTILINE) + + self.re_ShowdownAction = re.compile(r'\n(?P\n)' % player_re, re.MULTILINE) + # + # + self.re_CollectPot = re.compile(r'' % player_re, re.MULTILINE) + self.re_sitsOut = re.compile("^%s sits out" % player_re, re.MULTILINE) + self.re_ShownCards = re.compile("^Seat (?P[0-9]+): %s \(.*\) showed \[(?P.*)\].*" % 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.+(?=\*\*\* FLOP \*\*\*)|.+)" + # r"(\*\*\* FLOP \*\*\*(?P \[\S\S \S\S \S\S\].+(?=\*\*\* TURN \*\*\*)|.+))?" + # r"(\*\*\* TURN \*\*\* \[\S\S \S\S \S\S] (?P\[\S\S\].+(?=\*\*\* RIVER \*\*\*)|.+))?" + # r"(\*\*\* RIVER \*\*\* \[\S\S \S\S \S\S \S\S] (?P\[\S\S\].+))?", hand.handText,re.DOTALL) + + m = re.search('(?P.+(?=.+(?=.+(?=.+(?= 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) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 79dab478..691adae9 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -17,26 +17,26 @@ import os import sys -from optparse import OptionParser - - -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") -(options, sys.argv) = parser.parse_args() +import threading +import Options +import string +cl_options = string.join(sys.argv[1:]) +(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 logging + import pygtk pygtk.require('2.0') import gtk -import fpdb_db +import interlocks + + import fpdb_simple import GuiBulkImport import GuiPlayerStats @@ -45,6 +45,8 @@ import GuiTableViewer import GuiAutoImport import GuiGraphViewer import GuiSessionViewer +import SQL +import Database import FpdbSQLQueries import Configuration @@ -117,11 +119,13 @@ class fpdb: def dia_create_del_database(self, widget, data=None): print "todo: implement dia_create_del_database" self.obtain_global_lock() + self.release_global_lock() #end def dia_create_del_database def dia_create_del_user(self, widget, data=None): print "todo: implement dia_create_del_user" self.obtain_global_lock() + self.release_global_lock() #end def dia_create_del_user def dia_database_stats(self, widget, data=None): @@ -130,7 +134,7 @@ class fpdb: #end def dia_database_stats 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) sessions_tab=new_sessions_thread.get_vbox() self.add_and_display_tab(sessions_tab, "Sessions") @@ -138,16 +142,19 @@ class fpdb: def dia_delete_db_parts(self, widget, data=None): print "todo: implement dia_delete_db_parts" self.obtain_global_lock() + self.release_global_lock() #end def dia_delete_db_parts def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None): print "todo: implement dia_edit_profile" self.obtain_global_lock() + self.release_global_lock() #end def dia_edit_profile def dia_export_db(self, widget, data=None): print "todo: implement dia_export_db" self.obtain_global_lock() + self.release_global_lock() #end def dia_export_db def dia_get_db_root_credentials(self): @@ -173,6 +180,7 @@ class fpdb: def dia_import_db(self, widget, data=None): print "todo: implement dia_import_db" self.obtain_global_lock() + self.release_global_lock() #end def dia_import_db def dia_licensing(self, widget, data=None): @@ -181,19 +189,23 @@ class fpdb: def dia_load_profile(self, widget, data=None): """Dialogue to select a file to load a profile from""" - if self.obtain_global_lock() == 0: # returns 0 if successful - try: - chooser = gtk.FileChooserDialog(title="Please select a profile file to load", - action=gtk.FILE_CHOOSER_ACTION_OPEN, - buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) - chooser.set_filename(self.profile) + if self.obtain_global_lock(): # returns true if successful + #try: + # chooser = gtk.FileChooserDialog(title="Please select a profile file to load", + # action=gtk.FILE_CHOOSER_ACTION_OPEN, + # buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + # chooser.set_filename(self.profile) - response = chooser.run() - chooser.destroy() - if response == gtk.RESPONSE_OK: - self.load_profile(chooser.get_filename()) - elif response == gtk.RESPONSE_CANCEL: - print 'User cancelled loading profile' + # response = chooser.run() + # chooser.destroy() + # if response == gtk.RESPONSE_OK: + # self.load_profile(chooser.get_filename()) + # elif response == gtk.RESPONSE_CANCEL: + # print 'User cancelled loading profile' + #except: + # pass + try: + self.load_profile() except: pass self.release_global_lock() @@ -201,39 +213,41 @@ class fpdb: def dia_recreate_tables(self, widget, data=None): """Dialogue that asks user to confirm that he wants to delete and recreate the tables""" - if self.obtain_global_lock() 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: dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, 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 response = dia_confirm.run() dia_confirm.destroy() 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 # than lock all the other tables # ToDo: lock all other tables so that lock doesn't have to be released - self.release_global_lock() - lock_released = True - self.db.recreate_tables() - else: + # self.release_global_lock() + # lock_released = True + self.db.fdb.recreate_tables() + #else: # 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: print 'User cancelled recreating tables' except: pass - if not lock_released: - self.release_global_lock() + #if not lock_released: + self.release_global_lock() #end def dia_recreate_tables def dia_regression_test(self, widget, data=None): print "todo: implement dia_regression_test" self.obtain_global_lock() + self.release_global_lock() #end def dia_regression_test def dia_save_profile(self, widget, data=None): @@ -350,28 +364,27 @@ class fpdb: def load_profile(self): """Loads profile from the provided path name.""" + self.config = Configuration.Config(file=options.config, dbname=options.dbname) self.settings = {} + self.settings['global_lock'] = self.lock if (os.sep=="/"): self.settings['os']="linuxmac" else: 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_tv_parameters()) self.settings.update(self.config.get_import_parameters()) 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 = fpdb_db.fpdb_db() - #print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName'] - self.db.connect(self.settings['db-backend'], - self.settings['db-host'], - self.settings['db-databaseName'], - self.settings['db-user'], - self.settings['db-password']) - if self.db.wrongDbVersion: + self.sql = SQL.Sql(type = self.settings['db-type'], db_server = self.settings['db-server']) + self.db = Database.Database(self.config, sql = self.sql) + + if self.db.fdb.wrongDbVersion: 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.") @@ -389,9 +402,16 @@ class fpdb: response = diaDbVersionWarning.run() 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 self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name()) - self.db.db.rollback() + self.db.connection.rollback() #end def load_profile def not_implemented(self, widget, data=None): @@ -399,17 +419,18 @@ class fpdb: #end def not_implemented def obtain_global_lock(self): - print "\nTaking global lock ..." - self.fdb_lock = fpdb_db.fpdb_db() - self.fdb_lock.connect(self.settings['db-backend'], - self.settings['db-host'], - self.settings['db-databaseName'], - self.settings['db-user'], - self.settings['db-password']) - return self.fdb_lock.get_global_lock() + ret = self.lock.acquire(False) # will return false if lock is already held + if ret: + print "\nGlobal lock taken ..." + else: + print "\nFailed to get global lock." + return ret + # need to release it later: + # self.lock.release() + #end def obtain_global_lock - def quit(self, widget, data): + def quit(self, widget, data=None): print "Quitting normally" #check if current settings differ from profile, if so offer to save or abort self.db.disconnect() @@ -417,9 +438,8 @@ class fpdb: #end def quit_cliecked def release_global_lock(self): - self.fdb_lock.db.rollback() - self.fdb_lock.disconnect() - print "Global lock released." + self.lock.release() + print "Global lock released.\n" #end def release_global_lock def tab_abbreviations(self, widget, data=None): @@ -444,13 +464,13 @@ class fpdb: #end def tab_bulk_import 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) ps_tab=new_ps_thread.get_vbox() self.add_and_display_tab(ps_tab, "Player Stats") 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) ps_tab=new_ps_thread.get_vbox() 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): """opens a table viewer tab""" #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) tv_tab=new_tv_thread.get_vbox() self.add_and_display_tab(tv_tab, "Table Viewer") @@ -477,17 +497,18 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") def tabGraphViewer(self, widget, data=None): """opens a graph viewer tab""" #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) gv_tab=new_gv_thread.get_vbox() self.add_and_display_tab(gv_tab, "Graphs") #end def tabGraphViewer def __init__(self): - self.threads=[] - self.db=None - self.config = Configuration.Config(dbname=options.dbname) - self.load_profile() + 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.status_bar = None self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 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.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.load_profile() sys.stderr.write("fpdb starting ...") #end def __init__ diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index e0ccd813..99724d7e 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -18,20 +18,21 @@ import os import re import sys +import logging from time import time, strftime import fpdb_simple import FpdbSQLQueries class fpdb_db: + MYSQL_INNODB = 2 + PGSQL = 3 + SQLITE = 4 def __init__(self): """Simple constructor, doesnt really do anything""" self.db = None self.cursor = None self.sql = {} - self.MYSQL_INNODB = 2 - self.PGSQL = 3 - self.SQLITE = 4 # Data Structures for index and foreign key creation # drop_code is an int with possible values: 0 - don't drop for bulk import @@ -72,6 +73,8 @@ class fpdb_db: , {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0} , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} ] + , [ # indexes for sqlite (list index 4) + ] ] self.foreignKeys = [ @@ -146,12 +149,12 @@ class fpdb_db: self.settings = {} self.settings['os'] = "linuxmac" if os.name != "nt" else "windows" - self.settings.update(config.get_db_parameters()) - self.connect(self.settings['db-backend'], - self.settings['db-host'], - self.settings['db-databaseName'], - self.settings['db-user'], - self.settings['db-password']) + db = config.get_db_parameters() + self.connect(backend=db['db-backend'], + host=db['db-host'], + database=db['db-databaseName'], + user=db['db-user'], + password=db['db-password']) #end def do_connect def connect(self, backend=None, host=None, database=None, @@ -164,13 +167,13 @@ class fpdb_db: self.user=user self.password=password self.database=database - if backend==self.MYSQL_INNODB: + if backend==fpdb_db.MYSQL_INNODB: import MySQLdb try: self.db = MySQLdb.connect(host = host, user = user, passwd = password, db = database, use_unicode=True) except: raise fpdb_simple.FpdbError("MySQL connection failed") - elif backend==self.PGSQL: + elif backend==fpdb_db.PGSQL: import psycopg2 import psycopg2.extensions psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) @@ -179,19 +182,41 @@ class fpdb_db: # For local domain-socket connections, only DB name is # needed, and everything else is in fact undefined and/or # 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": - self.db = psycopg2.connect(database = database) - else: - self.db = psycopg2.connect(host = host, - user = user, - password = password, - database = database) + try: + self.db = psycopg2.connect(database = database) + 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, + user = user, + password = password, + 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: raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) 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. self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) + self.cursor.execute(self.sql.query['set tx level']) self.wrongDbVersion=False try: self.cursor.execute("SELECT * FROM Settings") @@ -222,13 +247,14 @@ class fpdb_db: def create_tables(self): #todo: should detect and fail gracefully if tables already exist. + logging.debug(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['createGametypesTable']) self.cursor.execute(self.sql.query['createPlayersTable']) self.cursor.execute(self.sql.query['createAutoratesTable']) 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['createTourneysTable']) self.cursor.execute(self.sql.query['createTourneysPlayersTable']) @@ -260,10 +286,12 @@ class fpdb_db: for table in tables: self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade') elif(self.get_backend_name() == 'SQLite'): - #todo: sqlite version here - print "Empty function here" + self.cursor.execute(self.sql.query['list_tables']) + 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 def drop_referential_integrity(self): @@ -292,6 +320,8 @@ class fpdb_db: return "MySQL InnoDB" elif self.backend==3: return "PostgreSQL" + elif self.backend==4: + return "SQLite" else: raise fpdb_simple.FpdbError("invalid backend") #end def get_backend_name @@ -301,11 +331,15 @@ class fpdb_db: #end def get_db_info def fillDefaultData(self): - self.cursor.execute("INSERT INTO Settings VALUES (118);") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');") - self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');") + self.cursor.execute("INSERT INTO Settings (version) VALUES (118);") + self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')") + self.cursor.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', '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 + # (siteId,buyin,fee,knockout,rebuyOrAddon) VALUES + # (1,0,0,0,?)""",(False,) ) #end def fillDefaultData def recreate_tables(self): @@ -538,72 +572,100 @@ class fpdb_db: self.db.set_isolation_level(1) # go back to normal isolation level #end def dropAllIndexes - def analyzeDB(self): - """Do whatever the DB can offer to update index/table statistics""" - stime = time() - if self.backend == self.PGSQL: - self.db.set_isolation_level(0) # allow vacuum to work - try: - self.cursor.execute("vacuum analyze") - except: - print "Error during vacuum" - self.db.set_isolation_level(1) # go back to normal isolation level - self.db.commit() - atime = time() - stime - print "analyze took", atime, "seconds" - #end def analyzeDB - - # Currently uses an exclusive lock on the Hands table as a global lock - # Return values are Unix style, 0 for success, positive integers for errors - # 1 = generic error - # 2 = hands table does not exist (error message is suppressed) - def get_global_lock(self): + def getLastInsertId(self): if self.backend == self.MYSQL_INNODB: - try: - self.cursor.execute( "lock tables Hands write" ) - except: - # Table 'fpdb.hands' doesn't exist - if str(sys.exc_value).find(".hands' doesn't exist") >= 0: - return(2) - print "Error! failed to obtain global lock. Close all programs accessing " \ - + "database (including fpdb) and try again (%s)." \ - % ( str(sys.exc_value).rstrip('\n'), ) - return(1) + ret = self.db.insert_id() + if ret < 1 or ret > 999999999: + print "getLastInsertId(): problem fetching insert_id? ret=", ret + ret = -1 elif self.backend == self.PGSQL: - try: - self.cursor.execute( "lock table Hands in exclusive mode nowait" ) - #print "... after lock table, status =", self.cursor.statusmessage - except: - # relation "hands" does not exist - if str(sys.exc_value).find('relation "hands" does not exist') >= 0: - return(2) - print "Error! failed to obtain global lock. Close all programs accessing " \ - + "database (including fpdb) and try again (%s)." \ - % ( str(sys.exc_value).rstrip('\n'), ) - return(1) - return(0) + # some options: + # currval(hands_id_seq) - use name of implicit seq here + # lastval() - still needs sequences set up? + # insert ... returning is useful syntax (but postgres specific?) + # see rules (fancy trigger type things) + self.cursor.execute ("SELECT lastval()") + row = self.cursor.fetchone() + if not row: + print "getLastInsertId(%s): problem fetching lastval? row=" % seq, row + ret = -1 + else: + 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): #stores into table hands: - self.cursor.execute ("""INSERT INTO Hands - (siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats - ,playersVpi, playersAtStreet1, playersAtStreet2 - ,playersAtStreet3, playersAtStreet4, playersAtShowdown - ,street0Raises, street1Raises, street2Raises - ,street3Raises, street4Raises, street1Pot - ,street2Pot, street3Pot, street4Pot - ,showdownPot + self.cursor.execute ("""INSERT INTO Hands ( + tablename, + sitehandno, + gametypeid, + handstart, + importtime, + seats, + maxseats, + boardcard1, + boardcard2, + boardcard3, + boardcard4, + boardcard5, +-- texture, + playersVpi, + playersAtStreet1, + playersAtStreet2, + playersAtStreet3, + playersAtStreet4, + playersAtShowdown, + street0Raises, + street1Raises, + street2Raises, + street3Raises, + street4Raises, +-- street1Pot, +-- street2Pot, +-- street3Pot, +-- street4Pot, +-- showdownPot ) VALUES - (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""" - ,(p['siteHandNo'], gametype_id, p['handStart'], len(names), p['tableName'], datetime.datetime.today(), p['maxSeats'] - ,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2'] - ,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown'] - ,hudCache['street0Raises'], hudCache['street1Raises'], hudCache['street2Raises'] - ,hudCache['street3Raises'], hudCache['street4Raises'], hudCache['street1Pot'] - ,hudCache['street2Pot'], hudCache['street3Pot'], hudCache['street4Pot'] - ,hudCache['showdownPot'] - ) - ) + (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s)""", + ( + p['tablename'], + p['sitehandno'], + p['gametypeid'], + p['handStart'], + 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) #end class fpdb_db diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 9b779574..f13e333e 100644 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -22,6 +22,7 @@ import os # todo: remove this once import_dir is in fpdb_import import sys from time import time, strftime +import logging import traceback import math import datetime @@ -31,6 +32,7 @@ import re import fpdb_simple import fpdb_db +import Database import fpdb_parse_logic import Configuration @@ -57,7 +59,8 @@ class Importer: self.settings = settings self.caller = caller 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.filelist = {} self.dirlist = {} @@ -75,9 +78,13 @@ class Importer: self.settings.setdefault("minPrint", 30) 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.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 def setCallHud(self, value): @@ -162,12 +169,19 @@ class Importer: def runImport(self): """"Run full import on self.filelist.""" + 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': - 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': self.fdb.prepareBulkImport() + else: + print "No need drop indexes." + #print "dropInd =", self.settings['dropIndexes'], " dropHudCache =", self.settings['dropHudCache'] totstored = 0 totdups = 0 totpartial = 0 @@ -183,7 +197,13 @@ class Importer: tottime += ttime if self.settings['dropIndexes'] == 'drop': 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) # else: import threaded @@ -202,6 +222,41 @@ class Importer: if self.settings['handsInDB'] > 50000: return "don't 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. def runUpdated(self): #Check for new files in monitored directories @@ -253,36 +308,60 @@ class Importer: if os.path.isdir(file): self.addToDirList[file] = [site] + [filter] return - if filter == "passthrough" or filter == "": - (stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(file, site) - else: - conv = None - # Load filter, process file, pass returned filename to import_fpdb_file + + conv = None + # Load filter, process file, pass returned filename to import_fpdb_file - print "\nConverting %s" % file - hhbase = self.config.get_import_parameters().get("hhArchiveBase") - hhbase = os.path.expanduser(hhbase) - hhdir = os.path.join(hhbase,site) - try: - out_path = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file)) - except: - out_path = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file)) + print "\nConverting %s" % file + hhbase = self.config.get_import_parameters().get("hhArchiveBase") + hhbase = os.path.expanduser(hhbase) + hhdir = os.path.join(hhbase,site) + try: + out_path = os.path.join(hhdir, file.split(os.path.sep)[-2]+"-"+os.path.basename(file)) + except: + out_path = os.path.join(hhdir, "x"+strftime("%d-%m-%y")+os.path.basename(file)) - filter_name = filter.replace("ToFpdb", "") + filter_name = filter.replace("ToFpdb", "") - mod = __import__(filter) - obj = getattr(mod, filter_name, None) - if callable(obj): - conv = obj(in_path = file, out_path = out_path) - if(conv.getStatus()): - (stored, duplicates, partial, errors, ttime) = self.import_fpdb_file(out_path, site) - else: - # conversion didn't work - # TODO: appropriate response? - return (0, 0, 0, 1, 0) +# 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) + obj = getattr(mod, filter_name, None) + if callable(obj): + conv = obj(in_path = file, out_path = out_path, index = 0) # Index into file 0 until changeover + if(conv.getStatus() and self.NEWIMPORT == False): + (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: - print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter) - return + # conversion didn't work + # TODO: appropriate response? + return (0, 0, 0, 1, 0) + else: + print "Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter) + return #This will barf if conv.getStatus != True return (stored, duplicates, partial, errors, ttime) @@ -359,8 +438,9 @@ class Importer: self.hand=hand try: - handsId = fpdb_parse_logic.mainParser(self.settings['db-backend'], self.fdb.db - ,self.fdb.cursor, self.siteIds[site], category, hand, self.config) + handsId = fpdb_parse_logic.mainParser( self.settings, self.fdb + , self.siteIds[site], category, hand + , self.config, self.database ) self.fdb.db.commit() stored += 1 diff --git a/pyfpdb/fpdb_parse_logic.py b/pyfpdb/fpdb_parse_logic.py index 150707c1..bb361f63 100644 --- a/pyfpdb/fpdb_parse_logic.py +++ b/pyfpdb/fpdb_parse_logic.py @@ -18,10 +18,16 @@ #methods that are specific to holdem but not trivial import fpdb_simple -import fpdb_save_to_db +import Database #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]) 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 siteHandNo = fpdb_simple.parseSiteHandNo(hand[0]) + #print "siteHandNo =", siteHandNo handStartTime = fpdb_simple.parseHandStartTime(hand[0]) isTourney = fpdb_simple.isTourney(hand[0]) @@ -45,7 +52,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config): break #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: siteTourneyNo = fpdb_simple.parseTourneyNo(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 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) @@ -72,7 +79,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config): seatLines.append(line) 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) startCashes = tmp['startCashes'] seatNos = tmp['seatNos'] @@ -111,7 +118,7 @@ def mainParser(backend, db, cursor, siteID, category, hand, config): tableName = tableResult['tableName'] #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. fpdb_simple.convertCardValues(cardValues) if base == "hold": @@ -119,13 +126,14 @@ def mainParser(backend, db, cursor, siteID, category, hand, config): fpdb_simple.convertBlindBet(actionTypes, actionAmounts) fpdb_simple.checkPositions(positions) - cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, )) - limit_type = cursor.fetchone()[0] + fdb.cursor.execute("SELECT limitType FROM Gametypes WHERE id=%s",(gametypeID, )) + limit_type = fdb.cursor.fetchone()[0] fpdb_simple.convert3B4B(category, limit_type, actionTypes, actionAmounts) totalWinnings = sum(winnings) # 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": hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes , 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) if base == "hold": - result = fpdb_save_to_db.tourney_holdem_omaha( - config, backend, db, cursor, base, category, siteTourneyNo, buyin + result = db.tourney_holdem_omaha( + config, settings, fdb.db, fdb.cursor, base, category, siteTourneyNo, buyin , fee, knockout, entries, prizepool, tourneyStartTime , payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo , gametypeID, handStartTime, names, playerIDs, startCashes @@ -149,8 +157,8 @@ def mainParser(backend, db, cursor, siteID, category, hand, config): , winnings, rakes, actionTypes, allIns, actionAmounts , actionNos, hudImportData, maxSeats, tableName, seatNos) elif base == "stud": - result = fpdb_save_to_db.tourney_stud( - config, backend, db, cursor, base, category, siteTourneyNo + result = db.tourney_stud( + config, settings, fdb.db, fdb.cursor, base, category, siteTourneyNo , buyin, fee, knockout, entries, prizepool, tourneyStartTime , payin_amounts, ranks, tourneyTypeId, siteID, siteHandNo , gametypeID, handStartTime, names, playerIDs, startCashes @@ -161,23 +169,23 @@ def mainParser(backend, db, cursor, siteID, category, hand, config): raise fpdb_simple.FpdbError("unrecognised category") else: if base == "hold": - result = fpdb_save_to_db.ring_holdem_omaha( - config, backend, db, cursor, base, category, siteHandNo + result = db.ring_holdem_omaha( + config, settings, fdb.db, fdb.cursor, base, category, siteHandNo , gametypeID, handStartTime, names, playerIDs , startCashes, positions, cardValues, cardSuits , boardValues, boardSuits, winnings, rakes , actionTypes, allIns, actionAmounts, actionNos , hudImportData, maxSeats, tableName, seatNos) elif base == "stud": - result = fpdb_save_to_db.ring_stud( - config, backend, db, cursor, base, category, siteHandNo, gametypeID + result = db.ring_stud( + config, settings, fdb.db, fdb.cursor, base, category, siteHandNo, gametypeID , handStartTime, names, playerIDs, startCashes, antes , cardValues, cardSuits, winnings, rakes, actionTypes, allIns , actionAmounts, actionNos, hudImportData, maxSeats, tableName , seatNos) else: raise fpdb_simple.FpdbError ("unrecognised category") - db.commit() + fdb.db.commit() return result #end def mainParser diff --git a/pyfpdb/fpdb_save_to_db.py b/pyfpdb/fpdb_save_to_db.py deleted file mode 100644 index 3870a6e9..00000000 --- a/pyfpdb/fpdb_save_to_db.py +++ /dev/null @@ -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 . -#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 diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 2b6f248b..3e862254 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -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) #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 - ,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: cursor.execute ("""INSERT INTO Hands (siteHandNo, gametypeId, handStart, seats, tableName, importTime, maxSeats + ,boardcard1,boardcard2,boardcard3,boardcard4,boardcard5 ,playersVpi, playersAtStreet1, playersAtStreet2 ,playersAtStreet3, playersAtStreet4, playersAtShowdown ,street0Raises, street1Raises, street2Raises @@ -1159,9 +1152,11 @@ def storeHands(backend, conn, cursor, site_hand_no, gametype_id ,showdownPot ) VALUES - (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + (%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 + ,cards[0], cards[1], cards[2], cards[3], cards[4] ,hudCache['playersVpi'], hudCache['playersAtStreet1'], hudCache['playersAtStreet2'] ,hudCache['playersAtStreet3'], hudCache['playersAtStreet4'], hudCache['playersAtShowdown'] ,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 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']) for player in xrange(len(playerIds)): @@ -2292,189 +2287,6 @@ VALUES (%s, %s, %s, %s, %s, %s, pass # else: # 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 def store_tourneys(cursor, tourneyTypeId, siteTourneyNo, entries, prizepool, startTime): diff --git a/pyfpdb/interlocks.py b/pyfpdb/interlocks.py new file mode 100755 index 00000000..96edaa3e --- /dev/null +++ b/pyfpdb/interlocks.py @@ -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)