From 3892b3789d3b28f51b9f4f4f493fd8188a005138 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 26 May 2009 16:10:27 +0800 Subject: [PATCH] Session breakdown Proof of Concept Only prints some of the session breakdown to stdout at the moment, otherwise is a copy of PlayerStats with a bunch of stuff commented out. Looks like: DEBUG: len(times) 337 DEBUG: len(diffs) 336 DEBUG: len(index[0]) 2 DEBUG: index [54 88] DEBUG: index[0][0] 54 Hands in session 0: 54 Start: 22/03/2009 07:04 End: 22/03/2009 07:49 Total: 2669 Hands in session 1: 33 Start: 24/03/2009 17:10 End: 24/03/2009 17:35 Total: 1482 I think the Total number has an index incorrect at the moment. --- pyfpdb/GuiSessionViewer.py | 310 +++++++++++++++++++++++++++++++++++++ pyfpdb/fpdb.py | 9 ++ 2 files changed, 319 insertions(+) create mode 100644 pyfpdb/GuiSessionViewer.py 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/fpdb.py b/pyfpdb/fpdb.py index 66ed87cf..2bd413a4 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() @@ -267,6 +274,7 @@ class fpdb: + @@ -304,6 +312,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),