diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index fdb7edb3..d7550b38 100644 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -570,7 +570,8 @@ class GUICashStats(list): def get_defaults(self): """A list of defaults to be called, should there be no entry in config""" - defaults = [ [u'game', u'Game', True, True, u'%s', u'str', 0.0], + # SQL column name, display title, display all, display positional, format, type, alignment + defaults = [ [u'game', u'Game', True, True, u'%s', u'str', 0.0], [u'hand', u'Hand', False, False, u'%s', u'str', 0.0], [u'plposition', u'Posn', False, False, u'%s', u'str', 1.0], [u'pname', u'Name', False, False, u'%s', u'str', 0.0], diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 68f2a462..0647baf2 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -5,17 +5,17 @@ Create and manage the database objects. """ # Copyright 2008-2010, Ray E. Barker -# +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -105,7 +105,7 @@ class Database: # Data Structures for index and foreign key creation # drop_code is an int with possible values: 0 - don't drop for bulk import # 1 - drop during bulk import - # db differences: + # db differences: # - note that mysql automatically creates indexes on constrained columns when # foreign keys are created, while postgres does not. Hence the much longer list # of indexes is required for postgres. @@ -147,7 +147,7 @@ class Database: ] , [ # indexes for sqlite (list index 4) {'tab':'Hands', 'col':'gametypeId', 'drop':0} - , {'tab':'HandsPlayers', 'col':'handId', 'drop':0} + , {'tab':'HandsPlayers', 'col':'handId', 'drop':0} , {'tab':'HandsPlayers', 'col':'playerId', 'drop':0} , {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0} , {'tab':'HandsActions', 'col':'handsPlayerId', 'drop':0} @@ -204,7 +204,7 @@ class Database: # (fkcol is used for foreigh key name) # mysql to list indexes: (CG - "LIST INDEXES" should work too) - # SELECT table_name, index_name, non_unique, column_name + # SELECT table_name, index_name, non_unique, column_name # FROM INFORMATION_SCHEMA.STATISTICS # WHERE table_name = 'tbl_name' # AND table_schema = 'db_name' @@ -241,7 +241,7 @@ class Database: # create index indexname on tablename (col); - def __init__(self, c, sql = None, autoconnect = True): + def __init__(self, c, sql = None, autoconnect = True): #log = Configuration.get_logger("logging.conf", "db", log_dir=c.dir_log) log.debug(_("Creating Database instance, sql = %s") % sql) self.config = c @@ -265,11 +265,11 @@ class Database: if autoconnect: # connect to db self.do_connect(c) - + 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_READ_COMMITTED = 1 #ISOLATION_LEVEL_SERIALIZABLE = 2 @@ -304,7 +304,7 @@ class Database: def dumpDatabase(self): result="fpdb database dump\nDB version=" + str(DB_VERSION)+"\n\n" - + tables=self.cursor.execute(self.sql.query['list_tables']) tables=self.cursor.fetchall() for table in (u'Actions', u'Autorates', u'Backings', u'Gametypes', u'Hands', u'HandsActions', u'HandsPlayers', u'HudCache', u'Players', u'RawHands', u'RawTourneys', u'Settings', u'Sites', u'TourneyTypes', u'Tourneys', u'TourneysPlayers'): @@ -328,7 +328,7 @@ class Database: result+="\n" return result #end def dumpDatabase - + # could be used by hud to change hud style def set_hud_style(self, style): self.hud_style = style @@ -368,7 +368,7 @@ class Database: self.database = database self.connection = None self.cursor = None - + if backend == Database.MYSQL_INNODB: import MySQLdb if use_pool: @@ -544,13 +544,13 @@ class Database: self.cursor.close() self.connection.close() self.__connected = False - + def reconnect(self, due_to_error=False): """Reconnects the DB""" #print "started reconnect" self.disconnect(due_to_error) self.connect(self.backend, self.host, self.database, self.user, self.password) - + def get_backend_name(self): """Returns the name of the currently used backend""" if self.backend==2: @@ -570,7 +570,7 @@ class Database: c.execute(self.sql.query['get_table_name'], (hand_id, )) row = c.fetchone() return row - + def get_table_info(self, hand_id): c = self.connection.cursor() c.execute(self.sql.query['get_table_name'], (hand_id, )) @@ -591,18 +591,18 @@ class Database: c.execute(self.sql.query['get_last_hand']) row = c.fetchone() return row[0] - + def get_xml(self, hand_id): c = self.connection.cursor() c.execute(self.sql.query['get_xml'], (hand_id)) row = c.fetchone() return row[0] - + def get_recent_hands(self, last_hand): c = self.connection.cursor() c.execute(self.sql.query['get_recent_hands'], {'last_hand': last_hand}) return c.fetchall() - + def get_hand_info(self, new_hand_id): c = self.connection.cursor() c.execute(self.sql.query['get_hand_info'], new_hand_id) @@ -835,7 +835,7 @@ class Database: query = query.replace("", 'signed ') else: query = query.replace("", '') - + subs = (self.hand_1day_ago, hand, hero_id, seats_min, seats_max , hero_id, h_seats_min, h_seats_max) c = self.get_cursor() @@ -864,7 +864,7 @@ class Database: #print "DEBUG: stat_dict[%s][%s]: %s" %(playerid, name.lower(), val) stat_dict[playerid][name.lower()] += val n += 1 - if n >= 10000: break # todo: don't think this is needed so set nice and high + if n >= 10000: break # todo: don't think this is needed so set nice and high # prevents infinite loop so leave for now - comment out or remove? row = c.fetchone() else: @@ -874,7 +874,7 @@ class Database: #print "session stat_dict =", stat_dict #return stat_dict - + def get_player_id(self, config, siteName, playerName): c = self.connection.cursor() siteNameUtf = Charset.to_utf8(siteName) @@ -886,7 +886,7 @@ class Database: return row[0] else: return None - + def get_player_names(self, config, site_id=None, like_player_name="%"): """Fetch player names from players. Use site_id and like_player_name if provided""" @@ -897,7 +897,7 @@ class Database: c.execute(self.sql.query['get_player_names'], (p_name, site_id, site_id)) rows = c.fetchall() return rows - + def get_site_id(self, site): c = self.get_cursor() c.execute(self.sql.query['getSiteId'], (site,)) @@ -941,7 +941,7 @@ class Database: def prepareBulkImport(self): - """Drop some indexes/foreign keys to prepare for bulk import. + """Drop some indexes/foreign keys to prepare for bulk import. Currently keeping the standalone indexes as needed to import quickly""" stime = time() c = self.get_cursor() @@ -959,7 +959,7 @@ class Database: "FROM information_schema.KEY_COLUMN_USAGE " + #"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb' "WHERE 1=1 " + - "AND table_name = %s AND column_name = %s " + + "AND table_name = %s AND column_name = %s " + "AND referenced_table_name = %s " + "AND referenced_column_name = %s ", (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) @@ -976,7 +976,7 @@ class Database: print "dropping pg fk", fk['fktab'], fk['fkcol'] try: # try to lock table to see if index drop will work: - # hmmm, tested by commenting out rollback in grapher. lock seems to work but + # hmmm, tested by commenting out rollback in grapher. lock seems to work but # then drop still hangs :-( does work in some tests though?? # will leave code here for now pending further tests/enhancement ... c.execute("BEGIN TRANSACTION") @@ -996,13 +996,13 @@ class Database: % (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n')) else: return -1 - + for idx in self.indexes[self.backend]: if idx['drop'] == 1: if self.backend == self.MYSQL_INNODB: print _("dropping mysql index "), idx['tab'], idx['col'] try: - # apparently nowait is not implemented in mysql so this just hangs if there are locks + # apparently nowait is not implemented in mysql so this just hangs if there are locks # preventing the index drop :-( c.execute( "alter table %s drop index %s;", (idx['tab'],idx['col']) ) except: @@ -1019,13 +1019,13 @@ class Database: #print "after lock, status:", c.statusmessage try: # table locked ok so index drop should work: - #print "drop index %s_%s_idx" % (idx['tab'],idx['col']) + #print "drop index %s_%s_idx" % (idx['tab'],idx['col']) c.execute( "drop index if exists %s_%s_idx" % (idx['tab'],idx['col']) ) #print "dropped pg index ", idx['tab'], idx['col'] except: if "does not exist" not in str(sys.exc_value): print _("warning: drop index %s_%s_idx failed: %s, continuing ...") \ - % (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n')) + % (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n')) c.execute("END TRANSACTION") except: print _("warning: index %s_%s_idx not dropped %s, continuing ...") \ @@ -1043,7 +1043,7 @@ class Database: def afterBulkImport(self): """Re-create any dropped indexes/foreign keys after bulk import""" stime = time() - + c = self.get_cursor() if self.backend == self.MYSQL_INNODB: c.execute("SET foreign_key_checks=1") @@ -1059,7 +1059,7 @@ class Database: "FROM information_schema.KEY_COLUMN_USAGE " + #"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb' "WHERE 1=1 " + - "AND table_name = %s AND column_name = %s " + + "AND table_name = %s AND column_name = %s " + "AND referenced_table_name = %s " + "AND referenced_column_name = %s ", (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) @@ -1070,8 +1070,8 @@ class Database: else: print _("Creating foreign key "), fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol'] try: - c.execute("alter table " + fk['fktab'] + " add foreign key (" - + fk['fkcol'] + ") references " + fk['rtab'] + "(" + c.execute("alter table " + fk['fktab'] + " add foreign key (" + + fk['fkcol'] + ") references " + fk['rtab'] + "(" + fk['rcol'] + ")") except: print _("Create foreign key failed: ") + str(sys.exc_info()) @@ -1086,7 +1086,7 @@ class Database: print _("Create foreign key failed: ") + str(sys.exc_info()) else: return -1 - + for idx in self.indexes[self.backend]: if idx['drop'] == 1: if self.backend == self.MYSQL_INNODB: @@ -1135,10 +1135,10 @@ class Database: c.execute("ALTER TABLE " + inner[j][0] + " DROP FOREIGN KEY " + key) self.commit() #end drop_referential_inegrity - + def recreate_tables(self): """(Re-)creates the tables of the current DB""" - + self.drop_tables() self.resetPlayerIDs() self.create_tables() @@ -1189,7 +1189,7 @@ class Database: self.rollback() raise #end def disconnect - + def drop_tables(self): """Drops the fpdb tables from the current db""" try: @@ -1333,7 +1333,7 @@ class Database: "FROM information_schema.KEY_COLUMN_USAGE " + #"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb' "WHERE 1=1 " + - "AND table_name = %s AND column_name = %s " + + "AND table_name = %s AND column_name = %s " + "AND referenced_table_name = %s " + "AND referenced_column_name = %s ", (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) @@ -1344,8 +1344,8 @@ class Database: else: print _("creating foreign key "), fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol'] try: - c.execute("alter table " + fk['fktab'] + " add foreign key (" - + fk['fkcol'] + ") references " + fk['rtab'] + "(" + c.execute("alter table " + fk['fktab'] + " add foreign key (" + + fk['fkcol'] + ") references " + fk['rtab'] + "(" + fk['rcol'] + ")") except: print _(" create foreign key failed: ") + str(sys.exc_info()) @@ -1382,7 +1382,7 @@ class Database: "FROM information_schema.KEY_COLUMN_USAGE " + #"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb' "WHERE 1=1 " + - "AND table_name = %s AND column_name = %s " + + "AND table_name = %s AND column_name = %s " + "AND referenced_table_name = %s " + "AND referenced_column_name = %s ", (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) @@ -1399,7 +1399,7 @@ class Database: print _("dropping pg foreign key"), fk['fktab'], fk['fkcol'] try: # try to lock table to see if index drop will work: - # hmmm, tested by commenting out rollback in grapher. lock seems to work but + # hmmm, tested by commenting out rollback in grapher. lock seems to work but # then drop still hangs :-( does work in some tests though?? # will leave code here for now pending further tests/enhancement ... c.execute("BEGIN TRANSACTION") @@ -1419,14 +1419,14 @@ class Database: % (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n')) else: print _("Only MySQL and Postgres supported so far") - + if self.backend == self.PGSQL: self.connection.set_isolation_level(1) # go back to normal isolation level #end def dropAllForeignKeys - + def fillDefaultData(self): - c = self.get_cursor() + c = self.get_cursor() c.execute("INSERT INTO Settings (version) VALUES (%s);" % (DB_VERSION)) #Fill Sites c.execute("INSERT INTO Sites (name,code) VALUES ('Full Tilt Poker', 'FT')") @@ -1458,7 +1458,7 @@ class Database: c.execute("INSERT INTO Actions (name,code) VALUES ('discards', 'D')") c.execute("INSERT INTO Actions (name,code) VALUES ('bringin', 'I')") c.execute("INSERT INTO Actions (name,code) VALUES ('completes', 'P')") - + #end def fillDefaultData def rebuild_indexes(self, start=None): @@ -1487,12 +1487,12 @@ class Database: p_id = self.get_player_id(self.config, site, self.hero[site_id]) if p_id: self.hero_ids[site_id] = int(p_id) - + if h_start is None: h_start = self.hero_hudstart_def if v_start is None: v_start = self.villain_hudstart_def - + if self.hero_ids == {}: where = "WHERE hp.tourneysPlayersId IS NULL" else: @@ -1509,7 +1509,7 @@ class Database: #print "rebuild_sql_cash:",rebuild_sql_cash self.get_cursor().execute(self.sql.query['clearHudCache']) self.get_cursor().execute(rebuild_sql_cash) - + if self.hero_ids == {}: where = "WHERE hp.tourneysPlayersId >= 0" else: @@ -1525,7 +1525,7 @@ class Database: rebuild_sql_tourney = rebuild_sql_tourney.replace('', ",t.tourneyTypeId") rebuild_sql_tourney = rebuild_sql_tourney.replace('', where) #print "rebuild_sql_tourney:",rebuild_sql_tourney - + self.get_cursor().execute(rebuild_sql_tourney) self.commit() print _("Rebuild hudcache took %.1f seconds") % (time() - stime,) @@ -1553,7 +1553,7 @@ class Database: p_id = self.get_player_id(self.config, site, self.hero[site_id]) if p_id: self.hero_ids[site_id] = int(p_id) - + q = self.sql.query['get_hero_hudcache_start'].replace("", str(tuple(self.hero_ids.values()))) c = self.get_cursor() c.execute(q) @@ -1634,20 +1634,20 @@ class Database: c = self.get_cursor() c.execute(q, ( - p['tableName'], - p['gameTypeId'], - p['siteHandNo'], + p['tableName'], + p['gameTypeId'], + p['siteHandNo'], p['tourneyId'], - p['startTime'], + p['startTime'], datetime.utcnow(), #importtime p['seats'], p['maxSeats'], p['texture'], p['playersVpi'], - p['boardcard1'], - p['boardcard2'], - p['boardcard3'], - p['boardcard4'], + p['boardcard1'], + p['boardcard2'], + p['boardcard3'], + p['boardcard4'], p['boardcard5'], p['playersAtStreet1'], p['playersAtStreet2'], @@ -1781,14 +1781,14 @@ class Database: #print "DEBUG: inserts: %s" %inserts #print "DEBUG: q: %s" % q c = self.get_cursor() - + if self.import_options['saveActions']: for r in inserts: c.execute(q, r) hpid[(r[0], r[1])] = self.get_last_insert_id(c) else: c.executemany(q, inserts) - + return hpid def storeHandsActions(self, hid, pids, hpid, adata, printdata = False): @@ -1834,12 +1834,12 @@ class Database: update_hudcache = update_hudcache.replace('%s', self.sql.query['placeholder']) insert_hudcache = self.sql.query['insert_hudcache'] insert_hudcache = insert_hudcache.replace('%s', self.sql.query['placeholder']) - + #print "DEBUG: %s %s %s" %(hid, pids, pdata) inserts = [] for p in pdata: line = [0]*85 - + line[0] = 1 # HDs if pdata[p]['street0VPI']: line[1] = 1 if pdata[p]['street0Aggr']: line[2] = 1 @@ -1939,7 +1939,7 @@ class Database: # Test statusmessage to see if update worked, do insert if not # num is a cursor in sqlite if ((self.backend == self.PGSQL and cursor.statusmessage != "UPDATE 1") - or (self.backend == self.MYSQL_INNODB and num == 0) + or (self.backend == self.MYSQL_INNODB and num == 0) or (self.backend == self.SQLITE and num.rowcount == 0)): #move the last 6 items in WHERE clause of row from the end of the array # to the beginning for the INSERT statement @@ -1963,7 +1963,7 @@ class Database: def getGameTypeId(self, siteid, game): c = self.get_cursor() #FIXME: Fixed for NL at the moment - c.execute(self.sql.query['getGametypeNL'], (siteid, game['type'], game['category'], game['limitType'], game['currency'], + c.execute(self.sql.query['getGametypeNL'], (siteid, game['type'], game['category'], game['limitType'], game['currency'], int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100))) tmp = c.fetchone() if (tmp == None): @@ -1985,7 +1985,7 @@ class Database: result = {} if(self.pcache == None): self.pcache = LambdaDict(lambda key:self.insertPlayer(key[0], key[1])) - + for player in pnames: result[player] = self.pcache[(player,siteid)] # NOTE: Using the LambdaDict does the same thing as: @@ -2069,7 +2069,7 @@ class Database: sendFinal = True else: self.store_the_hand(h) - # optional commit, could be every hand / every N hands / every time a + # optional commit, could be every hand / every N hands / every time a # commit message received?? mark flag to indicate if commits outstanding if commitEachHand: self.commit() @@ -2115,7 +2115,7 @@ class Database: def createTourneyType(self, hand):#note: this method is used on Hand and TourneySummary objects tourneyTypeId = 1 - + # Check if Tourney exists, and if so retrieve TTypeId : in that case, check values of the ttype cursor = self.get_cursor() cursor.execute (self.sql.query['getTourneyTypeIdByTourneyNo'].replace('%s', self.sql.query['placeholder']), @@ -2130,14 +2130,14 @@ class Database: # Check for an existing TTypeId that matches tourney info, if not found create it #print "info that we use to get TT by detail:", hand.siteId, hand.buyinCurrency, hand.buyin, hand.fee, hand.gametype['category'], hand.gametype['limitType'], hand.isKO, hand.isRebuy, hand.isAddOn, hand.speed, hand.isShootout, hand.isMatrix #print "the query:",self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']) - cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']), + cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']), (hand.siteId, hand.buyinCurrency, hand.buyin, hand.fee, hand.gametype['category'], hand.gametype['limitType'], hand.maxseats, hand.isKO, hand.isRebuy, hand.isAddOn, hand.speed, hand.isShootout, hand.isMatrix) ) result=cursor.fetchone() #print "result of fetching TT by details:",result - + try: tourneyTypeId = result[0] except TypeError: #this means we need to create a new entry @@ -2150,14 +2150,14 @@ class Database: tourneyTypeId = self.get_last_insert_id(cursor) return tourneyTypeId #end def createTourneyType - + def createOrUpdateTourney(self, hand, source):#note: this method is used on Hand and TourneySummary objects cursor = self.get_cursor() cursor.execute (self.sql.query['getTourneyByTourneyNo'].replace('%s', self.sql.query['placeholder']), (hand.siteId, hand.tourNo)) columnNames=[desc[0] for desc in cursor.description] result=cursor.fetchone() - + if result != None: if self.backend == Database.PGSQL: expectedValues = ('comment', 'tourneyname', 'matrixIdProcessed', 'totalRebuyCount', 'totalAddOnCount', @@ -2167,7 +2167,7 @@ class Database: 'prizepool', 'startTime', 'entries', 'commentTs', 'endTime') updateDb=False resultDict = dict(zip(columnNames, result)) - + tourneyId = resultDict["id"] if source=="TS": for ev in expectedValues : @@ -2196,7 +2196,7 @@ class Database: tourneyId = self.get_last_insert_id(cursor) return tourneyId #end def createOrUpdateTourney - + def createOrUpdateTourneysPlayers(self, hand, source):#note: this method is used on Hand and TourneySummary objects tourneysPlayersIds={} for player in hand.players: @@ -2206,7 +2206,7 @@ class Database: playerId = hand.dbid_pids[player[1]] else: raise FpdbParseError(_("invalid source in Database.createOrUpdateTourneysPlayers")) - + cursor = self.get_cursor() cursor.execute (self.sql.query['getTourneysPlayersByIds'].replace('%s', self.sql.query['placeholder']), (hand.tourneyId, playerId)) @@ -2217,14 +2217,14 @@ class Database: expectedValues = ('rank', 'winnings', 'winningsCurrency', 'rebuyCount', 'addOnCount', 'koCount') updateDb=False resultDict = dict(zip(columnNames, result)) - + tourneysPlayersIds[player[1]]=result[0] if source=="TS": for ev in expectedValues : handAttribute=ev if ev!="winnings" and ev!="winningsCurrency": handAttribute+="s" - + if getattr(hand, handAttribute)[player]==None and resultDict[ev]!=None:#DB has this value but object doesnt, so update object setattr(hand, handAttribute, resultDict[ev][player]) elif getattr(hand, handAttribute)[player]!=None and resultDict[ev]==None:#object has this value but DB doesnt, so update DB @@ -2250,7 +2250,7 @@ class Database: tourneysPlayersIds[player[1]]=self.get_last_insert_id(cursor) return tourneysPlayersIds #end def createOrUpdateTourneysPlayers - + def getTourneyTypesIds(self): c = self.connection.cursor() c.execute(self.sql.query['getTourneyTypesIds']) @@ -2262,31 +2262,31 @@ class Database: c = self.get_cursor() c.execute(self.sql.query['getTourneyInfo'], (siteName, tourneyNo)) columnNames=c.description - + names=[] for column in columnNames: names.append(column[0]) - + data=c.fetchone() return (names,data) #end def getTourneyInfo - + def getTourneyPlayerInfo(self, siteName, tourneyNo, playerName): c = self.get_cursor() c.execute(self.sql.query['getTourneyPlayerInfo'], (siteName, tourneyNo, playerName)) columnNames=c.description - + names=[] for column in columnNames: names.append(column[0]) - + data=c.fetchone() return (names,data) #end def getTourneyPlayerInfo #end class Database # Class used to hold all the data needed to write a hand to the db -# mainParser() in fpdb_parse_logic.py creates one of these and then passes it to +# mainParser() in fpdb_parse_logic.py creates one of these and then passes it to # self.insert_queue_hands() class HandToWrite: @@ -2334,7 +2334,7 @@ class HandToWrite: print _("HandToWrite.init error: ") + str(sys.exc_info()) raise # end def __init__ - + def set_all( self, config, settings, base, category, siteTourneyNo, buyin , fee, knockout, entries, prizepool, tourneyStartTime , isTourney, tourneyTypeId, siteID, siteHandNo @@ -2342,7 +2342,7 @@ class HandToWrite: , positions, antes, cardValues, cardSuits, boardValues, boardSuits , winnings, rakes, actionTypes, allIns, actionAmounts , actionNos, hudImportData, maxSeats, tableName, seatNos): - + try: self.config = config self.settings = settings @@ -2388,7 +2388,7 @@ class HandToWrite: def get_finished(self): return( self.finished ) # end def get_finished - + def get_siteHandNo(self): return( self.siteHandNo ) # end def get_siteHandNo @@ -2406,14 +2406,14 @@ if __name__=="__main__": # db_connection.recreate_tables() db_connection.dropAllIndexes() db_connection.createAllIndexes() - + h = db_connection.get_last_hand() print "last hand = ", h - + hero = db_connection.get_player_id(c, 'PokerStars', 'nutOmatic') if hero: print _("nutOmatic is id_player = %d") % hero - + # example of displaying query plan in sqlite: if db_connection.backend == 4: print @@ -2422,16 +2422,16 @@ if __name__=="__main__": for row in c.fetchall(): print _("query plan: "), row print - + t0 = time() stat_dict = db_connection.get_stats_from_hand(h, "ring") t1 = time() for p in stat_dict.keys(): print p, " ", stat_dict[p] - + print _("cards ="), db_connection.get_cards(u'1') db_connection.close_connection - + print _("get_stats took: %4.3f seconds") % (t1-t0) print _("press enter to continue") diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index cb289b7a..1f5a933e 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -175,6 +175,12 @@ class DerivedStats(): self.handsplayers[player]['rake'] = int(100* hand.rake)/len(hand.collectees) if self.handsplayers[player]['street1Seen'] == True: self.handsplayers[player]['wonWhenSeenStreet1'] = 1.0 + if self.handsplayers[player]['street2Seen'] == True: + self.handsplayers[player]['wonWhenSeenStreet2'] = 1.0 + if self.handsplayers[player]['street3Seen'] == True: + self.handsplayers[player]['wonWhenSeenStreet3'] = 1.0 + if self.handsplayers[player]['street4Seen'] == True: + self.handsplayers[player]['wonWhenSeenStreet4'] = 1.0 if self.handsplayers[player]['sawShowdown'] == True: self.handsplayers[player]['wonAtSD'] = 1.0 diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index 22977a26..26875070 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -66,13 +66,16 @@ class Everleaf(HandHistoryConverter): self.re_SitsOut = re.compile(ur"^%s sits out" % player_re, re.MULTILINE) def readSupportedGames(self): - return [["ring", "hold", "nl"], + return [ + ["ring", "hold", "nl"], ["ring", "hold", "pl"], ["ring", "hold", "fl"], - ["ring", "studhi", "fl"], - ["ring", "omahahi", "pl"], - ["ring", "omahahilo", "pl"], - ["tour", "hold", "nl"] + ["ring", "stud", "fl"], + #["ring", "omahahi", "pl"], + #["ring", "omahahilo", "pl"], + ["tour", "hold", "nl"], + ["tour", "hold", "fl"], + ["tour", "hold", "pl"] ] def determineGameType(self, handText): diff --git a/pyfpdb/Filters.py b/pyfpdb/Filters.py index a4afb24a..a4660eb2 100644 --- a/pyfpdb/Filters.py +++ b/pyfpdb/Filters.py @@ -62,6 +62,7 @@ class Filters(threading.Thread): gen = self.conf.get_general_params() self.day_start = 0 + if 'day_start' in gen: self.day_start = float(gen['day_start']) @@ -83,6 +84,7 @@ class Filters(threading.Thread): self.siteid = {} self.heroes = {} self.boxes = {} + self.graphops = {} for site in self.conf.get_supported_sites(): #Get db site id for filtering later @@ -103,6 +105,12 @@ class Filters(threading.Thread): self.sbGroups = {} self.numHands = 0 + # for use in graphops + # dspin = display in '$' or 'B' + self.graphops['dspin'] = "$" + self.graphops['showdown'] = 'OFF' + self.graphops['nonshowdown'] = 'OFF' + playerFrame = gtk.Frame() playerFrame.set_label_align(0.0, 0.0) vbox = gtk.VBox(False, 0) @@ -143,6 +151,16 @@ class Filters(threading.Thread): self.fillLimitsFrame(vbox, self.display) limitsFrame.add(vbox) + # GraphOps + graphopsFrame = gtk.Frame() + #graphops.set_label_align(0,0, 0.0) + graphopsFrame.show() + vbox = gtk.VBox(False, 0) + + self.fillGraphOpsFrame(vbox) + graphopsFrame.add(vbox) + + # Seats seatsFrame = gtk.Frame() seatsFrame.show() @@ -183,6 +201,7 @@ class Filters(threading.Thread): self.mainVBox.add(seatsFrame) self.mainVBox.add(groupsFrame) self.mainVBox.add(dateFrame) + self.mainVBox.add(graphopsFrame) self.mainVBox.add(self.Button1) self.mainVBox.add(self.Button2) @@ -203,6 +222,8 @@ class Filters(threading.Thread): groupsFrame.hide() if "Dates" not in self.display or self.display["Dates"] == False: dateFrame.hide() + if "GraphOps" not in self.display or self.display["GraphOps"] == False: + graphopsFrame.hide() if "Button1" not in self.display or self.display["Button1"] == False: self.Button1.hide() if "Button2" not in self.display or self.display["Button2"] == False: @@ -255,6 +276,9 @@ class Filters(threading.Thread): return self.heroes #end def getHeroes + def getGraphOps(self): + return self.graphops + def getLimits(self): ltuple = [] for l in self.limits: @@ -539,6 +563,14 @@ class Filters(threading.Thread): self.groups[group] = w.get_active() log.debug( _("self.groups[%s] set to %s") %(group, self.groups[group]) ) + + def __set_displayin_select(self, w, ops): + self.graphops['dspin'] = ops + + def __set_graphopscheck_select(self, w, data): + #print "%s was toggled %s" % (data, ("OFF", "ON")[w.get_active()]) + self.graphops[data] = ("OFF", "ON")[w.get_active()] + def fillPlayerFrame(self, vbox, display): top_hbox = gtk.HBox(False, 0) vbox.pack_start(top_hbox, False, False, 0) @@ -770,8 +802,58 @@ class Filters(threading.Thread): # set_active doesn't seem to call this for some reason so call manually: self.__set_limit_select(rb1, 'ring') self.type = 'ring' + top_hbox.pack_start(showb, expand=False, padding=1) + + def fillGraphOpsFrame(self, vbox): + top_hbox = gtk.HBox(False, 0) + vbox.pack_start(top_hbox, False, False, 0) + title = gtk.Label("Graphing Options:") + title.set_alignment(xalign=0.0, yalign=0.5) + top_hbox.pack_start(title, expand=True, padding=3) + showb = gtk.Button(label="hide", stock=None, use_underline=True) + showb.set_alignment(xalign=1.0, yalign=0.5) + showb.connect('clicked', self.__toggle_box, 'games') top_hbox.pack_start(showb, expand=False, padding=1) + hbox1 = gtk.HBox(False, 0) + vbox.pack_start(hbox1, False, False, 0) + hbox1.show() + + label = gtk.Label("Show Graph In:") + label.set_alignment(xalign=0.0, yalign=0.5) + hbox1.pack_start(label, True, True, 0) + label.show() + + button = gtk.RadioButton(None, "$$") + hbox1.pack_start(button, True, True, 0) + button.connect("toggled", self.__set_displayin_select, "$") + button.set_active(True) + button.show() + + button = gtk.RadioButton(button, "BB") + hbox1.pack_start(button, True, True, 0) + button.connect("toggled", self.__set_displayin_select, "BB") + button.show() + + vbox1 = gtk.VBox(False, 0) + vbox.pack_start(vbox1, False, False, 0) + vbox1.show() + + button = gtk.CheckButton("Showdown Winnings", False) + vbox1.pack_start(button, True, True, 0) + # wouldn't it be awesome if there was a way to remember the state of things like + # this and be able to set it to what it was last time? + #button.set_active(True) + button.connect("toggled", self.__set_graphopscheck_select, "showdown") + button.show() + + button = gtk.CheckButton("Non-Showdown Winnings", False) + vbox1.pack_start(button, True, True, 0) + # ditto as 8 lines up :) + #button.set_active(True) + button.connect("toggled", self.__set_graphopscheck_select, "nonshowdown"); + button.show() + def fillSeatsFrame(self, vbox, display): hbox = gtk.HBox(False, 0) vbox.pack_start(hbox, False, False, 0) diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 9d080f99..0121509d 100755 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -45,7 +45,7 @@ if os.name == "nt": class GuiAutoImport (threading.Thread): - def __init__(self, settings, config, sql, parent): + def __init__(self, settings, config, sql = None, parent = None, cli = False): self.importtimer = 0 self.settings = settings self.config = config @@ -54,9 +54,6 @@ class GuiAutoImport (threading.Thread): imp = self.config.get_import_parameters() -# print "Import parameters" -# print imp - self.input_settings = {} self.pipe_to_hud = None @@ -66,13 +63,21 @@ class GuiAutoImport (threading.Thread): self.importer.setQuiet(False) self.importer.setFailOnError(False) self.importer.setHandCount(0) -# self.importer.setWatchTime() self.server = settings['db-host'] self.user = settings['db-user'] self.password = settings['db-password'] self.database = settings['db-databaseName'] + if cli == False: + self.setupGui() + else: + # TODO: Separate the code that grabs the directories from config + # Separate the calls to the Importer API + # Create a timer interface that doesn't rely on GTK + pass + + def setupGui(self): self.mainVBox = gtk.VBox(False,1) hbox = gtk.HBox(True, 0) # contains 2 equal vboxes @@ -355,4 +360,4 @@ if __name__== "__main__": main_window.show() gtk.main() else: - pass + i = GuiAutoImport(settings, config, cli = True) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index e43657c8..443d65aa 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -35,8 +35,10 @@ import Filters import Charset try: + calluse = not 'matplotlib' in sys.modules import matplotlib - matplotlib.use('GTKCairo') + if calluse: + matplotlib.use('GTKCairo') from matplotlib.figure import Figure from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar @@ -73,6 +75,7 @@ class GuiGraphViewer (threading.Thread): "Seats" : False, "SeatSep" : False, "Dates" : True, + "GraphOps" : True, "Groups" : False, "Button1" : True, "Button2" : True @@ -144,11 +147,8 @@ class GuiGraphViewer (threading.Thread): siteids = self.filters.getSiteIds() limits = self.filters.getLimits() games = self.filters.getGames() - graphs = { - "profit" : True, - "sawShowdown" : True, - "nonShowdown" : True - } + graphops = self.filters.getGraphOps() + names = "" for i in ('show', 'none'): if i in limits: @@ -161,6 +161,7 @@ class GuiGraphViewer (threading.Thread): result = self.db.get_player_id(self.conf, site, _hname) if result is not None: playerids.append(int(result)) + names = names + "\n"+_hname + " on "+site if not sitenos: #Should probably pop up here. @@ -183,13 +184,15 @@ class GuiGraphViewer (threading.Thread): #Get graph data from DB starttime = time() - (green, blue, red) = self.getRingProfitGraph(playerids, sitenos, limits, games) + (green, blue, red) = self.getRingProfitGraph(playerids, sitenos, limits, games, graphops['dspin']) print _("Graph generated in: %s") %(time() - starttime) + #Set axis labels and grid overlay properites self.ax.set_xlabel(_("Hands"), fontsize = 12) - self.ax.set_ylabel("$", fontsize = 12) + # SET LABEL FOR X AXIS + self.ax.set_ylabel(graphops['dspin'], fontsize = 12) self.ax.grid(color='g', linestyle=':', linewidth=0.2) if green == None or green == []: self.ax.set_title(_("No Data for Player(s) Found")) @@ -225,15 +228,15 @@ class GuiGraphViewer (threading.Thread): #TODO: Do something useful like alert user #print "No hands returned by graph query" else: - self.ax.set_title(_("Profit graph for ring games")) + self.ax.set_title(_("Profit graph for ring games"+names),fontsize=12) #Draw plot - if graphs['profit'] == True: - self.ax.plot(green, color='green', label=_('Hands: %d\nProfit: $%.2f') %(len(green), green[-1])) - if graphs['sawShowdown'] == True: - self.ax.plot(blue, color='blue', label=_('Showdown: $%.2f') %(blue[-1])) - if graphs['nonShowdown'] == True: - self.ax.plot(red, color='red', label=_('Non-showdown: $%.2f') %(red[-1])) + self.ax.plot(green, color='green', label=_('Hands: %d\nProfit (%s): %.2f') %(len(green),graphops['dspin'], green[-1])) + if graphops['showdown'] == 'ON': + self.ax.plot(blue, color='blue', label=_('Showdown (%s): %.2f') %(graphops['dspin'], blue[-1])) + if graphops['nonshowdown'] == 'ON': + self.ax.plot(red, color='red', label=_('Non-showdown (%s): %.2f') %(graphops['dspin'], red[-1])) + if sys.version[0:3] == '2.5': self.ax.legend(loc='upper left', shadow=True, prop=FontProperties(size='smaller')) else: @@ -249,9 +252,17 @@ class GuiGraphViewer (threading.Thread): #end of def showClicked - def getRingProfitGraph(self, names, sites, limits, games): - tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite'] + + def getRingProfitGraph(self, names, sites, limits, games, units): +# tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite'] # print "DEBUG: getRingProfitGraph" + + if units == '$': + tmp = self.sql.query['getRingProfitAllHandsPlayerIdSiteInDollars'] + elif units == 'BB': + tmp = self.sql.query['getRingProfitAllHandsPlayerIdSiteInBB'] + + start_date, end_date = self.filters.getDates() #Buggered if I can find a way to do this 'nicely' take a list of integers and longs diff --git a/pyfpdb/GuiRingPlayerStats.py b/pyfpdb/GuiRingPlayerStats.py index 55e6e9fa..d601e0d2 100644 --- a/pyfpdb/GuiRingPlayerStats.py +++ b/pyfpdb/GuiRingPlayerStats.py @@ -34,10 +34,72 @@ import Filters import Charset import GuiPlayerStats +from TreeViewTooltips import TreeViewTooltips + + #colalias,colshowsumm,colshowposn,colheading,colxalign,colformat,coltype = 0,1,2,3,4,5,6 #new order in config file: colalias,colheading,colshowsumm,colshowposn,colformat,coltype,colxalign = 0,1,2,3,4,5,6 ranks = {'x':0, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, 'T':10, 'J':11, 'Q':12, 'K':13, 'A':14} +onlinehelp = {'Game':_('Type of Game'), + 'Hand':_('Hole cards'), + 'Posn':_('Position'), + 'Name':_('Name of the player'), + 'Hds':_('Number of hands played'), + 'Seats':_('Number of Seats'), + 'VPIP':_('Voluntarily Putting In the pot\n(blinds excluded)'), + 'PFR':_('% Pre Flop Raise'), + 'PF3':_('% Pre Flop Re-Raise / 3Bet'), + 'AggFac':_('Aggression Factor\n'), + 'AggFreq':_('Aggression Frequency\nBet or Raise vs Fold'), + 'ContBet':_('Continuation Bet on the flop'), + 'RFI':_('% Raise First In\% Raise when first to bet'), + 'Steals':_('% First to raise pre-flop\nand steal blinds'), + 'Saw_F':_('% Saw Flop vs hands dealt'), + 'SawSD':_('Saw Show Down / River'), + 'WtSDwsF':_('Went To Show Down When Saw Flop'), + 'W$SD':_('Amount Won when Show Down seen'), + 'FlAFq':_('Flop Aggression\n% Bet or Raise after seeing Flop'), + 'TuAFq':_('Turn Aggression\n% Bet or Raise after seeing Turn'), + 'RvAFq':_('River Aggression\n% Bet or Raise after seeing River'), + 'PoFAFq':_('Coming Soon\nTotal % agression'), + 'Net($)':_('Amount won'), + 'bb/100':_('Number of Big Blinds won\nor lost per 100 hands'), + 'Rake($)':_('Amount of rake paid'), + 'bbxr/100':_('Number of Big Blinds won\nor lost per 100 hands\nwhen excluding rake'), + 'Variance':_('Measure of uncertainty\nThe lower, the more stable the amounts won') + } + + + +class DemoTips(TreeViewTooltips): + + def __init__(self, customer_column): + # customer_column is an instance of gtk.TreeViewColumn and + # is being used in the gtk.TreeView to show customer names. + # self.cust_col = customer_column + + # call base class init + TreeViewTooltips.__init__(self) + + def get_tooltip(self, view, column, path): + model = view.get_model() + cards = model[path][0] + + title=column.get_title() + display='%s\n%s' % (title,onlinehelp[title]) + return (display) + + def location(self, x, y, w, h): + # rename me to "location" so I override the base class + # method. This will demonstrate being able to change + # where the tooltip window popups, relative to the + # pointer. + + # this will place the tooltip above and to the right + return x + 30, y - (h + 10) + + class GuiRingPlayerStats (GuiPlayerStats.GuiPlayerStats): @@ -354,6 +416,7 @@ class GuiRingPlayerStats (GuiPlayerStats.GuiPlayerStats): print _("***sortcols error: ") + str(sys.exc_info()[1]) print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] ) #end def sortcols + def addGrid(self, vbox, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games): counter = 0 @@ -466,6 +529,9 @@ class GuiRingPlayerStats (GuiPlayerStats.GuiPlayerStats): #print treerow sqlrow += 1 row += 1 + tips = DemoTips(column[colformat]) + tips.add_view(view) + vbox.show_all() view.show() if len(self.liststore) == 1: diff --git a/pyfpdb/GuiSessionViewer.py b/pyfpdb/GuiSessionViewer.py old mode 100755 new mode 100644 index 53360680..d12f1d10 --- a/pyfpdb/GuiSessionViewer.py +++ b/pyfpdb/GuiSessionViewer.py @@ -30,7 +30,7 @@ try: calluse = not 'matplotlib' in sys.modules import matplotlib if calluse: - matplotlib.use('GTK') + matplotlib.use('GTKCairo') from matplotlib.figure import Figure from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar diff --git a/pyfpdb/GuiTourneyGraphViewer.py b/pyfpdb/GuiTourneyGraphViewer.py index a1a5cc7d..04f3b626 100644 --- a/pyfpdb/GuiTourneyGraphViewer.py +++ b/pyfpdb/GuiTourneyGraphViewer.py @@ -35,8 +35,10 @@ import Filters import Charset try: + calluse = not 'matplotlib' in sys.modules import matplotlib - matplotlib.use('GTKCairo') + if calluse: + matplotlib.use('GTKCairo') from matplotlib.figure import Figure from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar diff --git a/pyfpdb/HUD_config.test.xml b/pyfpdb/HUD_config.test.xml index 5ec16a56..6df2a4ea 100644 --- a/pyfpdb/HUD_config.test.xml +++ b/pyfpdb/HUD_config.test.xml @@ -2,7 +2,7 @@ - +