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:
+