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

This commit is contained in:
sqlcoder 2009-06-04 20:29:58 +01:00
commit 401aca4c12
12 changed files with 376 additions and 24 deletions

View File

@ -43,6 +43,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising Betfair converter class") logging.info("Initialising Betfair converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 7 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

View File

@ -54,6 +54,7 @@ class CarbonPoker(HandHistoryConverter):
print "Initialising Carbon Poker converter class" print "Initialising Carbon Poker converter class"
HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init
self.setFileType("xml") self.setFileType("xml")
self.siteId = 4 # Needs to match id entry in Sites database
def readSupportedGames(self): def readSupportedGames(self):
pass pass

View File

@ -49,6 +49,7 @@ debugging: if False, pass on partially supported game types. If true, have a go
logging.info("Initialising Everleaf converter class") logging.info("Initialising Everleaf converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 3 # Needs to match id entry in Sites database
self.debugging = debugging self.debugging = debugging
if autostart: if autostart:
self.start() self.start()

View File

@ -45,6 +45,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising Fulltilt converter class") logging.info("Initialising Fulltilt converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 1 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

310
pyfpdb/GuiSessionViewer.py Normal file
View File

@ -0,0 +1,310 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import threading
import pygtk
pygtk.require('2.0')
import gtk
import os
from time import time, strftime, localtime
from numpy import diff, nonzero
import Card
import fpdb_import
import fpdb_db
import Filters
import FpdbSQLQueries
class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, debug=True):
self.debug=debug
self.conf=config
self.MYSQL_INNODB = 2
self.PGSQL = 3
self.SQLITE = 4
# create new db connection to avoid conflicts with other threads
self.db = fpdb_db.fpdb_db()
self.db.do_connect(self.conf)
self.cursor=self.db.cursor
self.sql = querylist
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())
# text used on screen stored here so that it can be configured
self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
}
filters_display = { "Heroes" : True,
"Sites" : True,
"Games" : False,
"Limits" : True,
"LimitSep" : True,
"Seats" : True,
"SeatSep" : True,
"Dates" : False,
"Groups" : True,
"Button1" : True,
"Button2" : True
}
self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display)
self.filters.registerButton2Name("_Refresh")
self.filters.registerButton2Callback(self.refreshStats)
# ToDo: store in config
# ToDo: create popup to adjust column config
# columns to display, keys match column name returned by sql, values in tuple are:
# is column displayed, column heading, xalignment, formatting
self.columns = [ ("game", True, "Game", 0.0, "%s")
, ("hand", False, "Hand", 0.0, "%s") # true not allowed for this line
, ("n", True, "Hds", 1.0, "%d")
, ("avgseats", True, "Seats", 1.0, "%3.1f")
, ("vpip", True, "VPIP", 1.0, "%3.1f")
, ("pfr", True, "PFR", 1.0, "%3.1f")
, ("pf3", True, "PF3", 1.0, "%3.1f")
, ("steals", True, "Steals", 1.0, "%3.1f")
, ("saw_f", True, "Saw_F", 1.0, "%3.1f")
, ("sawsd", True, "SawSD", 1.0, "%3.1f")
, ("wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f")
, ("wmsd", True, "W$SD", 1.0, "%3.1f")
, ("flafq", True, "FlAFq", 1.0, "%3.1f")
, ("tuafq", True, "TuAFq", 1.0, "%3.1f")
, ("rvafq", True, "RvAFq", 1.0, "%3.1f")
, ("pofafq", False, "PoFAFq", 1.0, "%3.1f")
, ("net", True, "Net($)", 1.0, "%6.2f")
, ("bbper100", True, "BB/100", 1.0, "%4.2f")
, ("rake", True, "Rake($)", 1.0, "%6.2f")
, ("variance", True, "Variance", 1.0, "%5.2f")
]
self.stats_frame = None
self.stats_vbox = None
self.detailFilters = [] # the data used to enhance the sql select
self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show()
self.stats_frame = gtk.Frame()
self.stats_frame.show()
self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox)
self.fillStatsFrame(self.stats_vbox)
self.main_hbox.pack_start(self.filters.get_vbox())
self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
################################
# make sure Hand column is not displayed
[x for x in self.columns if x[0] == 'hand'][0][1] == False
def get_vbox(self):
"""returns the vbox of this thread"""
return self.main_hbox
def refreshStats(self, widget, data):
try: self.stats_vbox.destroy()
except AttributeError: pass
self.stats_vbox = gtk.VBox(False, 0)
self.stats_vbox.show()
self.stats_frame.add(self.stats_vbox)
self.fillStatsFrame(self.stats_vbox)
def fillStatsFrame(self, vbox):
sites = self.filters.getSites()
heroes = self.filters.getHeroes()
siteids = self.filters.getSiteIds()
limits = self.filters.getLimits()
seats = self.filters.getSeats()
sitenos = []
playerids = []
# Which sites are selected?
for site in sites:
if sites[site] == True:
sitenos.append(siteids[site])
self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],))
result = self.db.cursor.fetchall()
if len(result) == 1:
playerids.append(result[0][0])
if not sitenos:
#Should probably pop up here.
print "No sites selected - defaulting to PokerStars"
sitenos = [2]
if not playerids:
print "No player ids found"
return
if not limits:
print "No limits found"
return
self.createStatsTable(vbox, playerids, sitenos, limits, seats)
def createStatsTable(self, vbox, playerids, sitenos, limits, seats):
starttime = time()
# Display summary table at top of page
# 3rd parameter passes extra flags, currently includes:
# holecards - whether to display card breakdown (True/False)
flags = [False]
self.addTable(vbox, 'playerDetailedStats', flags, playerids, sitenos, limits, seats)
# Separator
sep = gtk.HSeparator()
vbox.pack_start(sep, expand=False, padding=3)
sep.show_now()
vbox.show_now()
heading = gtk.Label(self.filterText['handhead'])
heading.show()
vbox.pack_start(heading, expand=False, padding=3)
# Scrolled window for detailed table (display by hand)
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
swin.show()
vbox.pack_start(swin, expand=True, padding=3)
vbox1 = gtk.VBox(False, 0)
vbox1.show()
swin.add_with_viewport(vbox1)
# Detailed table
flags = [True]
self.addTable(vbox1, 'playerDetailedStats', flags, playerids, sitenos, limits, seats)
self.db.db.commit()
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
#end def fillStatsFrame(self, vbox):
def addTable(self, vbox, query, flags, playerids, sitenos, limits, seats):
row = 0
sqlrow = 0
colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4
if not flags: holecards = False
else: holecards = flags[0]
self.stats_table = gtk.Table(1, 1, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
self.db.cursor.execute("""select UNIX_TIMESTAMP(handStart) as time, id from Hands ORDER BY time""")
THRESHOLD = 1800
hands = self.db.cursor.fetchall()
times = map(lambda x:long(x[0]), hands)
handids = map(lambda x:int(x[1]), hands)
print "DEBUG: len(times) %s" %(len(times))
diffs = diff(times)
print "DEBUG: len(diffs) %s" %(len(diffs))
index = nonzero(diff(times) > THRESHOLD)
print "DEBUG: len(index[0]) %s" %(len(index[0]))
print "DEBUG: index %s" %(index)
print "DEBUG: index[0][0] %s" %(index[0][0])
total = 0
last_idx = 0
for i in range(len(index[0])):
print "Hands in session %4s: %4s Start: %s End: %s Total: %s" %(i, index[0][i] - last_idx, strftime("%d/%m/%Y %H:%M", localtime(times[last_idx])), strftime("%d/%m/%Y %H:%M", localtime(times[index[0][i]])), times[index[0][i]] - times[last_idx])
total = total + (index[0][i] - last_idx)
last_idx = index[0][i] + 1
print "Total: ", total
#
# colnames = [desc[0].lower() for desc in self.cursor.description]
#
# # pre-fetch some constant values:
# cols_to_show = [x for x in self.columns if x[colshow]]
# hgametypeid_idx = colnames.index('hgametypeid')
#
# liststore = gtk.ListStore(*([str] * len(cols_to_show)))
# view = gtk.TreeView(model=liststore)
# view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
# vbox.pack_start(view, expand=False, padding=3)
# textcell = gtk.CellRendererText()
# numcell = gtk.CellRendererText()
# numcell.set_property('xalign', 1.0)
# listcols = []
#
# # Create header row eg column: ("game", True, "Game", 0.0, "%s")
# for col, column in enumerate(cols_to_show):
# if column[colalias] == 'game' and holecards:
# s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
# else:
# s = column[colheading]
# listcols.append(gtk.TreeViewColumn(s))
# view.append_column(listcols[col])
# if column[colformat] == '%s':
# if col == 1 and holecards:
# listcols[col].pack_start(textcell, expand=True)
# else:
# listcols[col].pack_start(textcell, expand=False)
# listcols[col].add_attribute(textcell, 'text', col)
# else:
# listcols[col].pack_start(numcell, expand=False)
# listcols[col].add_attribute(numcell, 'text', col)
#
# rows = len(result) # +1 for title row
#
# while sqlrow < rows:
# treerow = []
# if(row%2 == 0):
# bgcolor = "white"
# else:
# bgcolor = "lightgrey"
# for col,column in enumerate(cols_to_show):
# if column[colalias] in colnames:
# value = result[sqlrow][colnames.index(column[colalias])]
# else:
# if column[colalias] == 'game':
# if holecards:
# value = Card.twoStartCardString( result[sqlrow][hgametypeid_idx] )
# else:
# minbb = result[sqlrow][colnames.index('minbigblind')]
# maxbb = result[sqlrow][colnames.index('maxbigblind')]
# value = result[sqlrow][colnames.index('limittype')] + ' ' \
# + result[sqlrow][colnames.index('category')].title() + ' ' \
# + result[sqlrow][colnames.index('name')] + ' $'
# if 100 * int(minbb/100.0) != minbb:
# value += '%.2f' % (minbb/100.0)
# else:
# value += '%.0f' % (minbb/100.0)
# if minbb != maxbb:
# if 100 * int(maxbb/100.0) != maxbb:
# value += ' - $' + '%.2f' % (maxbb/100.0)
# else:
# value += ' - $' + '%.0f' % (maxbb/100.0)
# else:
# continue
# if value and value != -999:
# treerow.append(column[colformat] % value)
# else:
# treerow.append(' ')
# iter = liststore.append(treerow)
# sqlrow += 1
# row += 1
vbox.show_all()

View File

@ -86,6 +86,7 @@ Should not commit, and do minimal selects. Callers may want to cache commits
db: a connected fpdb_db object""" db: a connected fpdb_db object"""
# TODO: # TODO:
# Players - base playerid and siteid tuple # Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
# HudCache data to come from DerivedStats class # HudCache data to come from DerivedStats class
# HandsActions - all actions for all players for all streets - self.actions # HandsActions - all actions for all players for all streets - self.actions
# BoardCards - ? # BoardCards - ?
@ -100,6 +101,29 @@ db: a connected fpdb_db object"""
""" Function to create Hand object from database """ """ Function to create Hand object from database """
pass pass
# Get SQL player IDs from database
# this version could also be improved upon using list comprehensions, etc
#def recognisePlayerIDs(cursor, names, site_id):
# result = []
# notfound = []
# cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names))
# tmp = dict(cursor.fetchall())
# for n in names:
# if n not in tmp:
# notfound.append(n)
# else:
# result.append(tmp[n])
# if notfound:
# cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound))
# cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound))
# tmp = cursor.fetchall()
# for n in tmp:
# result.append(n[0])
#
# return result
def addPlayer(self, seat, name, chips): def addPlayer(self, seat, name, chips):
"""\ """\

View File

@ -72,6 +72,7 @@ class OnGame(HandHistoryConverter):
HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init. HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init.
self.sitename = "OnGame" self.sitename = "OnGame"
self.setFileType("text", "cp1252") self.setFileType("text", "cp1252")
self.siteId = 5 # Needs to match id entry in Sites database
#self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)') #self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n\n+') self.rexx.setSplitHandRegex('\n\n\n+')

View File

@ -44,6 +44,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising PokerStars converter class") logging.info("Initialising PokerStars converter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 2 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

View File

@ -42,6 +42,7 @@ follow : whether to tail -f the input"""
logging.info("Initialising UltimateBetconverter class") logging.info("Initialising UltimateBetconverter class")
self.filetype = "text" self.filetype = "text"
self.codepage = "cp1252" self.codepage = "cp1252"
self.siteId = 6 # Needs to match id entry in Sites database
if autostart: if autostart:
self.start() self.start()

View File

@ -44,6 +44,7 @@ import GuiPositionalStats
import GuiTableViewer import GuiTableViewer
import GuiAutoImport import GuiAutoImport
import GuiGraphViewer import GuiGraphViewer
import GuiSessionViewer
import FpdbSQLQueries import FpdbSQLQueries
import Configuration import Configuration
@ -128,6 +129,12 @@ class fpdb:
#string=fpdb_db.getDbStats(db, cursor) #string=fpdb_db.getDbStats(db, cursor)
#end def dia_database_stats #end def dia_database_stats
def dia_database_sessions(self, widget, data=None):
new_sessions_thread=GuiSessionViewer.GuiSessionViewer(self.config, self.querydict)
self.threads.append(new_sessions_thread)
sessions_tab=new_sessions_thread.get_vbox()
self.add_and_display_tab(sessions_tab, "Sessions")
def dia_delete_db_parts(self, widget, data=None): def dia_delete_db_parts(self, widget, data=None):
print "todo: implement dia_delete_db_parts" print "todo: implement dia_delete_db_parts"
self.obtain_global_lock() self.obtain_global_lock()
@ -286,6 +293,7 @@ class fpdb:
<menuitem action="createuser"/> <menuitem action="createuser"/>
<menuitem action="createtabs"/> <menuitem action="createtabs"/>
<menuitem action="stats"/> <menuitem action="stats"/>
<menuitem action="sessions"/>
</menu> </menu>
<menu action="help"> <menu action="help">
<menuitem action="Abbrev"/> <menuitem action="Abbrev"/>
@ -323,6 +331,7 @@ class fpdb:
('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user), ('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user),
('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables), ('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables),
('stats', None, '_Statistics (todo)', None, 'View Database Statistics', self.dia_database_stats), ('stats', None, '_Statistics (todo)', None, 'View Database Statistics', self.dia_database_stats),
('sessions', None, 'Sessions', None, 'View Sessions', self.dia_database_sessions),
('help', None, '_Help'), ('help', None, '_Help'),
('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations), ('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations),
('About', None, 'A_bout', None, 'About the program', self.dia_about), ('About', None, 'A_bout', None, 'About the program', self.dia_about),

View File

@ -96,7 +96,7 @@ class fpdb_db:
try: try:
self.cursor.execute("SELECT * FROM Settings") self.cursor.execute("SELECT * FROM Settings")
settings=self.cursor.fetchone() settings=self.cursor.fetchone()
if settings[0]!=118: if settings[0]!=119:
print "outdated or too new database version - please recreate tables" print "outdated or too new database version - please recreate tables"
self.wrongDbVersion=True self.wrongDbVersion=True
except:# _mysql_exceptions.ProgrammingError: except:# _mysql_exceptions.ProgrammingError:
@ -201,10 +201,14 @@ class fpdb_db:
#end def get_db_info #end def get_db_info
def fillDefaultData(self): def fillDefaultData(self):
self.cursor.execute("INSERT INTO Settings VALUES (118);") self.cursor.execute("INSERT INTO Settings VALUES (119);")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');") 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, 'PokerStars', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Carbon', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'OnGame', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'UltimateBet', 'USD');")
self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Betfair', 'USD');")
self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);")
#end def fillDefaultData #end def fillDefaultData
@ -217,4 +221,23 @@ class fpdb_db:
self.db.commit() self.db.commit()
print "Finished recreating tables" print "Finished recreating tables"
#end def recreate_tables #end def recreate_tables
#end class fpdb_db
def getSqlPlayerIDs(names, site_id):
result = []
notfound = []
self.cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names))
tmp = dict(self.cursor.fetchall())
for n in names:
if n not in tmp:
notfound.append(n)
else:
result.append(tmp[n])
if notfound:
cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound))
cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound))
tmp = cursor.fetchall()
for n in tmp:
result.append(n[0])
#We proabably want to cache this
return result

View File

@ -1390,27 +1390,6 @@ def recognisePlayerIDs(cursor, names, site_id):
#end def recognisePlayerIDs #end def recognisePlayerIDs
# Here's a version that would work if it wasn't for the fact that it needs to have the output in the same order as input
# this version could also be improved upon using list comprehensions, etc
#def recognisePlayerIDs(cursor, names, site_id):
# result = []
# notfound = []
# cursor.execute("SELECT name,id FROM Players WHERE name='%s'" % "' OR name='".join(names))
# tmp = dict(cursor.fetchall())
# for n in names:
# if n not in tmp:
# notfound.append(n)
# else:
# result.append(tmp[n])
# if notfound:
# cursor.executemany("INSERT INTO Players (name, siteId) VALUES (%s, "+str(site_id)+")", (notfound))
# cursor.execute("SELECT id FROM Players WHERE name='%s'" % "' OR name='".join(notfound))
# tmp = cursor.fetchall()
# for n in tmp:
# result.append(n[0])
#
# return result
#recognises the name in the given line and returns its array position in the given array #recognises the name in the given line and returns its array position in the given array
def recognisePlayerNo(line, names, atype): def recognisePlayerNo(line, names, atype):