diff --git a/pyfpdb/BetfairToFpdb.py b/pyfpdb/BetfairToFpdb.py index bc1d17c9..672e858a 100755 --- a/pyfpdb/BetfairToFpdb.py +++ b/pyfpdb/BetfairToFpdb.py @@ -43,6 +43,7 @@ follow : whether to tail -f the input""" logging.info("Initialising Betfair converter class") self.filetype = "text" self.codepage = "cp1252" + self.siteId = 7 # Needs to match id entry in Sites database if autostart: self.start() diff --git a/pyfpdb/CarbonToFpdb.py b/pyfpdb/CarbonToFpdb.py index cf9fc8d3..fa1ad6fd 100644 --- a/pyfpdb/CarbonToFpdb.py +++ b/pyfpdb/CarbonToFpdb.py @@ -54,6 +54,7 @@ class CarbonPoker(HandHistoryConverter): print "Initialising Carbon Poker converter class" HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init self.setFileType("xml") + self.siteId = 4 # Needs to match id entry in Sites database def readSupportedGames(self): pass diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index f353bc61..6ed2b6ba 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -49,6 +49,7 @@ debugging: if False, pass on partially supported game types. If true, have a go logging.info("Initialising Everleaf converter class") self.filetype = "text" self.codepage = "cp1252" + self.siteId = 3 # Needs to match id entry in Sites database self.debugging = debugging if autostart: self.start() diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index dd0927b9..a84f683c 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -45,6 +45,7 @@ follow : whether to tail -f the input""" logging.info("Initialising Fulltilt converter class") self.filetype = "text" self.codepage = "cp1252" + self.siteId = 1 # Needs to match id entry in Sites database if autostart: self.start() diff --git a/pyfpdb/GuiSessionViewer.py b/pyfpdb/GuiSessionViewer.py new file mode 100644 index 00000000..51398b0f --- /dev/null +++ b/pyfpdb/GuiSessionViewer.py @@ -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 . +#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() diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 17eefe12..a51b710a 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -86,6 +86,7 @@ Should not commit, and do minimal selects. Callers may want to cache commits db: a connected fpdb_db object""" # TODO: # 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 # HandsActions - all actions for all players for all streets - self.actions # BoardCards - ? @@ -100,6 +101,29 @@ db: a connected fpdb_db object""" """ Function to create Hand object from database """ 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): """\ diff --git a/pyfpdb/OnGameToFpdb.py b/pyfpdb/OnGameToFpdb.py index ee16eb41..8a68b105 100755 --- a/pyfpdb/OnGameToFpdb.py +++ b/pyfpdb/OnGameToFpdb.py @@ -72,6 +72,7 @@ class OnGame(HandHistoryConverter): HandHistoryConverter.__init__(self, config, file, sitename="OnGame") # Call super class init. self.sitename = "OnGame" self.setFileType("text", "cp1252") + self.siteId = 5 # Needs to match id entry in Sites database #self.rexx.setGameInfoRegex('.*Blinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+)') self.rexx.setSplitHandRegex('\n\n\n+') diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 300c6071..2b4ec6a1 100755 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -44,6 +44,7 @@ follow : whether to tail -f the input""" logging.info("Initialising PokerStars converter class") self.filetype = "text" self.codepage = "cp1252" + self.siteId = 2 # Needs to match id entry in Sites database if autostart: self.start() diff --git a/pyfpdb/UltimateBetToFpdb.py b/pyfpdb/UltimateBetToFpdb.py index 6b11d8e6..b57e789e 100755 --- a/pyfpdb/UltimateBetToFpdb.py +++ b/pyfpdb/UltimateBetToFpdb.py @@ -42,6 +42,7 @@ follow : whether to tail -f the input""" logging.info("Initialising UltimateBetconverter class") self.filetype = "text" self.codepage = "cp1252" + self.siteId = 6 # Needs to match id entry in Sites database if autostart: self.start() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 234bb95f..645ddef5 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -44,6 +44,7 @@ import GuiPositionalStats import GuiTableViewer import GuiAutoImport import GuiGraphViewer +import GuiSessionViewer import FpdbSQLQueries import Configuration @@ -128,6 +129,12 @@ class fpdb: #string=fpdb_db.getDbStats(db, cursor) #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): print "todo: implement dia_delete_db_parts" self.obtain_global_lock() @@ -286,6 +293,7 @@ class fpdb: + @@ -323,6 +331,7 @@ class fpdb: ('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), ('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'), ('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations), ('About', None, 'A_bout', None, 'About the program', self.dia_about), diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 6e48fcf4..ad599b13 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -96,7 +96,7 @@ class fpdb_db: try: self.cursor.execute("SELECT * FROM Settings") settings=self.cursor.fetchone() - if settings[0]!=118: + if settings[0]!=119: print "outdated or too new database version - please recreate tables" self.wrongDbVersion=True except:# _mysql_exceptions.ProgrammingError: @@ -201,10 +201,14 @@ class fpdb_db: #end def get_db_info 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, 'PokerStars', '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);") #end def fillDefaultData @@ -217,4 +221,23 @@ class fpdb_db: self.db.commit() print "Finished recreating 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 diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index c6f79d9d..2241feab 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1390,27 +1390,6 @@ def recognisePlayerIDs(cursor, names, site_id): #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 def recognisePlayerNo(line, names, atype):