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

This commit is contained in:
grindi 2010-07-14 22:18:42 +04:00
commit 959e0e11cf
23 changed files with 665 additions and 176 deletions

View File

@ -1,5 +1,5 @@
# Variable definitions
VERSION = 0.12
VERSION = 0.20
DATE = $(shell date +%Y%m%d)
all:

View File

@ -1,5 +1,5 @@
README.txt
updated 26 March 2009, REB
updated 22 February 2010, REB
fpdb - Free Poker Database
@ -29,7 +29,7 @@ fpdb supports:
Omaha (incl Hi/low)
7 Card Stud (incl Hi/low)
Razz
Draw support is under development
Triple Draw and Badugi
Mixed Games -- HUD under development
Operating Systems:
@ -38,23 +38,38 @@ fpdb supports:
Mac OS/X -- no support for HUD
Databases:
SQLite configured by default
MySQL
PostgreSQL
SQLite under development
Downloads:
Releases: http://sourceforge.net/project/showfiles.php?group_id=226872
Development code via git: http://www.assembla.com/spaces/free_poker_tools/trac_git_tool
Developers:
At least 7 people have contributed code or patches. Others are welcome.
At least 10 people have contributed code or patches. Others are welcome.
Source Code:
If you received fpdb as the Windows compressed exe, then you did not
receive souce code for fpdb or the included libraries. If you wish, you can
obtain the source code here:
fpdb: see Downloads, above.
python: http://python.org/
gtk: http://www.gtk.org/download.html
pygtk: http://www.pygtk.org/downloads.html
psycopg2: http://initd.org/pub/software/psycopg/
mysqldb: http://sourceforge.net/projects/mysql-python/files/
sqlalchemy: http://www.sqlalchemy.org/download.html
numpy: http://www.scipy.org/Download
matplotlib: http://sourceforge.net/projects/matplotlib/files/
License
=======
Trademarks of third parties have been used under Fair Use or similar laws.
Copyright 2008 Steffen Jobbagy-Felso
Copyright 2009 Ray E. Barker
Copyright 2009,2010 Ray E. Barker
Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License,
Version 1.2 as published by the Free Software Foundation; with

View File

@ -414,6 +414,7 @@ class Import:
self.hhArchiveBase = node.getAttribute("hhArchiveBase")
self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True)
self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False)
self.saveStarsHH = string_to_bool(node.getAttribute("saveStarsHH"), default=False)
def __str__(self):
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \
@ -469,7 +470,8 @@ class Config:
self.file = file
self.dir_self = get_exec_path()
self.dir_config = os.path.dirname(self.file)
# self.dir_config = os.path.dirname(self.file)
self.dir_config = get_default_config_path()
self.dir_log = os.path.join(self.dir_config, 'log')
self.dir_database = os.path.join(self.dir_config, 'database')
self.log_file = os.path.join(self.dir_log, 'fpdb-log.txt')
@ -481,12 +483,19 @@ class Config:
print "\nReading configuration file %s\n" % file
try:
doc = xml.dom.minidom.parse(file)
self.file_error = None
except:
log.error("Error parsing %s. See error log file." % (file))
traceback.print_exc(file=sys.stderr)
print "press enter to continue"
sys.stdin.readline()
sys.exit()
self.file_error = sys.exc_info()[1]
# we could add a parameter to decide whether to return or read a line and exit?
return
#print "press enter to continue"
#sys.stdin.readline()
#sys.exit()
#ExpatError: not well-formed (invalid token): line 511, column 4
#sys.exc_info = (<class 'xml.parsers.expat.ExpatError'>, ExpatError('not well-formed (invalid token): line 511,
# column 4',), <traceback object at 0x024503A0>)
self.doc = doc
self.supported_sites = {}
@ -688,18 +697,8 @@ class Config:
try: db['db-server'] = self.supported_databases[name].db_server
except: pass
if self.supported_databases[name].db_server== DATABASE_TYPE_MYSQL:
db['db-backend'] = 2
elif self.supported_databases[name].db_server== DATABASE_TYPE_POSTGRESQL:
db['db-backend'] = 3
elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE:
db['db-backend'] = 4
# sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252)
# feel free to remove or improve this if you understand the problems
# better than me (not hard!)
Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True
else:
raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server)
db['db-backend'] = self.get_backend(self.supported_databases[name].db_server)
return db
def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None,
@ -718,6 +717,23 @@ class Config:
if db_server is not None: self.supported_databases[db_name].dp_server = db_server
if db_type is not None: self.supported_databases[db_name].dp_type = db_type
return
def get_backend(self, name):
"""Returns the number of the currently used backend"""
if name == DATABASE_TYPE_MYSQL:
ret = 2
elif name == DATABASE_TYPE_POSTGRESQL:
ret = 3
elif name == DATABASE_TYPE_SQLITE:
ret = 4
# sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252)
# feel free to remove or improve this if you understand the problems
# better than me (not hard!)
Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True
else:
raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server)
return ret
def getDefaultSite(self):
"Returns first enabled site or None"
@ -808,8 +824,12 @@ class Config:
try: imp['saveActions'] = self.imp.saveActions
except: imp['saveActions'] = True
try: imp['saveStarsHH'] = self.imp.saveStarsHH
except: imp['saveStarsHH'] = False
try: imp['fastStoreHudCache'] = self.imp.fastStoreHudCache
except: imp['fastStoreHudCache'] = True
return imp
def get_default_paths(self, site = None):

View File

@ -142,14 +142,18 @@ class Database:
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
, [ # indexes for sqlite (list index 4)
# {'tab':'Players', 'col':'name', 'drop':0} unique indexes not dropped
# {'tab':'Hands', 'col':'siteHandNo', 'drop':0} unique indexes not dropped
{'tab':'Hands', 'col':'gametypeId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'handId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'playerId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0}
#, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0} unique indexes not dropped
, {'tab':'HudCache', 'col':'gametypeId', 'drop':1}
, {'tab':'HudCache', 'col':'playerId', 'drop':0}
, {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0}
, {'tab':'Players', 'col':'siteId', 'drop':1}
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
]
]
@ -226,7 +230,7 @@ class Database:
# create index indexname on tablename (col);
def __init__(self, c, sql = None):
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
@ -247,41 +251,42 @@ class Database:
else:
self.sql = sql
# 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_SERIALIZABLE = 2
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_SERIALIZABLE = 2
if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion:
log.info("sqlite/:memory: - creating")
self.recreate_tables()
self.wrongDbVersion = False
if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion:
log.info("sqlite/:memory: - creating")
self.recreate_tables()
self.wrongDbVersion = False
self.pcache = None # PlayerId cache
self.cachemiss = 0 # Delete me later - using to count player cache misses
self.cachehit = 0 # Delete me later - using to count player cache hits
self.pcache = None # PlayerId cache
self.cachemiss = 0 # Delete me later - using to count player cache misses
self.cachehit = 0 # Delete me later - using to count player cache hits
# config while trying out new hudcache mechanism
self.use_date_in_hudcache = True
# config while trying out new hudcache mechanism
self.use_date_in_hudcache = True
#self.hud_hero_style = 'T' # Duplicate set of vars just for hero - not used yet.
#self.hud_hero_hands = 2000 # Idea is that you might want all-time stats for others
#self.hud_hero_days = 30 # but last T days or last H hands for yourself
#self.hud_hero_style = 'T' # Duplicate set of vars just for hero - not used yet.
#self.hud_hero_hands = 2000 # Idea is that you might want all-time stats for others
#self.hud_hero_days = 30 # but last T days or last H hands for yourself
# vars for hand ids or dates fetched according to above config:
self.hand_1day_ago = 0 # max hand id more than 24 hrs earlier than now
self.date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD)
self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero
self.date_nhands_ago = {} # dates N hands ago per player - not used yet
# vars for hand ids or dates fetched according to above config:
self.hand_1day_ago = 0 # max hand id more than 24 hrs earlier than now
self.date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD)
self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero
self.date_nhands_ago = {} # dates N hands ago per player - not used yet
self.saveActions = False if self.import_options['saveActions'] == False else True
self.saveActions = False if self.import_options['saveActions'] == False else True
self.connection.rollback() # make sure any locks taken so far are released
self.connection.rollback() # make sure any locks taken so far are released
#end def __init__
# could be used by hud to change hud style
@ -313,7 +318,7 @@ class Database:
self.__connected = True
def connect(self, backend=None, host=None, database=None,
user=None, password=None):
user=None, password=None, create=False):
"""Connects a database with the given parameters"""
if backend is None:
raise FpdbError('Database backend not defined')
@ -377,6 +382,7 @@ class Database:
print msg
raise FpdbError(msg)
elif backend == Database.SQLITE:
create = True
import sqlite3
if use_pool:
sqlite3 = pool.manage(sqlite3, pool_size=1)
@ -384,32 +390,35 @@ class Database:
# log.warning("SQLite won't work well without 'sqlalchemy' installed.")
if database != ":memory:":
if not os.path.isdir(self.config.dir_database):
if not os.path.isdir(self.config.dir_database) and create:
print "Creating directory: '%s'" % (self.config.dir_database)
log.info("Creating directory: '%s'" % (self.config.dir_database))
os.mkdir(self.config.dir_database)
database = os.path.join(self.config.dir_database, database)
self.db_path = database
log.info("Connecting to SQLite: %(database)s" % {'database':self.db_path})
self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES )
sqlite3.register_converter("bool", lambda x: bool(int(x)))
sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
self.connection.create_function("floor", 1, math.floor)
tmp = sqlitemath()
self.connection.create_function("mod", 2, tmp.mod)
if use_numpy:
self.connection.create_aggregate("variance", 1, VARIANCE)
if os.path.exists(database) or create:
self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES )
sqlite3.register_converter("bool", lambda x: bool(int(x)))
sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
self.connection.create_function("floor", 1, math.floor)
tmp = sqlitemath()
self.connection.create_function("mod", 2, tmp.mod)
if use_numpy:
self.connection.create_aggregate("variance", 1, VARIANCE)
else:
log.warning("Some database functions will not work without NumPy support")
self.cursor = self.connection.cursor()
self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes
self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish
else:
log.warning("Some database functions will not work without NumPy support")
self.cursor = self.connection.cursor()
self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes
self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish
raise FpdbError("sqlite database "+database+" does not exist")
else:
raise FpdbError("unrecognised database backend:"+backend)
raise FpdbError("unrecognised database backend:"+str(backend))
self.cursor = self.connection.cursor()
self.cursor.execute(self.sql.query['set tx level'])
self.check_version(database=database, create=True)
self.check_version(database=database, create=create)
def check_version(self, database, create):
@ -721,6 +730,8 @@ class Database:
# now get the stats
c.execute(self.sql.query[query], subs)
#for row in c.fetchall(): # needs "explain query plan" in sql statement
# print "query plan: ", row
colnames = [desc[0] for desc in c.description]
for row in c.fetchall():
playerid = row[0]
@ -907,7 +918,6 @@ class Database:
print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \
% (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n'))
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in self.indexes[self.backend]:
@ -942,7 +952,6 @@ class Database:
print "warning: index %s_%s_idx not dropped %s, continuing ..." \
% (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n'))
else:
print "Error: Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
@ -997,7 +1006,6 @@ class Database:
except:
print " create fk failed: " + str(sys.exc_info())
else:
print "Only MySQL and Postgres supported so far"
return -1
for idx in self.indexes[self.backend]:
@ -1019,7 +1027,6 @@ class Database:
except:
print " create index failed: " + str(sys.exc_info())
else:
print "Only MySQL and Postgres supported so far"
return -1
if self.backend == self.PGSQL:
@ -2096,6 +2103,7 @@ class HandToWrite:
if __name__=="__main__":
c = Configuration.Config()
sql = SQL.Sql(db_server = 'sqlite')
db_connection = Database(c) # mysql fpdb holdem
# db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem
@ -2113,12 +2121,25 @@ if __name__=="__main__":
if hero:
print "nutOmatic is id_player = %d" % hero
# example of displaying query plan in sqlite:
if db_connection.backend == 4:
print
c = db_connection.get_cursor()
c.execute('explain query plan '+sql.query['get_table_name'], (h, ))
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"
sys.stdin.readline()

View File

@ -162,7 +162,7 @@ class DerivedStats():
self.handsplayers[player]['wonAtSD'] = 1.0
for player in hand.pot.committed:
self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player]))
self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])- (100*hand.pot.common[player]))
self.calcCBets(hand)
@ -227,7 +227,7 @@ class DerivedStats():
#print "bb =", bb, "sb =", sb, "players =", players
for i,player in enumerate(reversed(players)):
self.handsplayers[player]['position'] = str(i)
self.handsplayers[player]['position'] = i
def assembleHudCache(self, hand):
# No real work to be done - HandsPlayers data already contains the correct info
@ -305,12 +305,13 @@ class DerivedStats():
"""Fills stealAttempt(Chance|ed, fold(Bb|Sb)ToSteal(Chance|)
Steal attempt - open raise on positions 1 0 S - i.e. MP3, CO, BU, SB
(note: I don't think PT2 counts SB steals in HU hands, maybe we shouldn't?)
Fold to steal - folding blind after steal attemp wo any other callers or raisers
"""
steal_attempt = False
steal_positions = ('1', '0', 'S')
steal_positions = (1, 0, 'S')
if hand.gametype['base'] == 'stud':
steal_positions = ('2', '1', '0')
steal_positions = (2, 1, 0)
for action in hand.actions[hand.actionStreets[1]]:
pname, act = action[0], action[1]
posn = self.handsplayers[pname]['position']
@ -344,9 +345,9 @@ class DerivedStats():
for action in hand.actions[hand.actionStreets[1]]:
# FIXME: fill other(3|4)BStreet0 - i have no idea what does it mean
pname, aggr = action[0], action[1] in ('raises', 'bets')
self.handsplayers[pname]['street0_3BChance'] = bet_level == 2
self.handsplayers[pname]['street0_3BChance'] = self.handsplayers[pname]['street0_3BChance'] or bet_level == 2
self.handsplayers[pname]['street0_4BChance'] = bet_level == 3
self.handsplayers[pname]['street0_3BDone'] = aggr and (self.handsplayers[pname]['street0_3BChance'])
self.handsplayers[pname]['street0_3BDone'] = self.handsplayers[pname]['street0_3BDone'] or (aggr and self.handsplayers[pname]['street0_3BChance'])
self.handsplayers[pname]['street0_4BDone'] = aggr and (self.handsplayers[pname]['street0_4BChance'])
if aggr:
bet_level += 1

View File

@ -211,6 +211,9 @@ class Filters(threading.Thread):
self.Button2.connect("clicked", self.callback['button2'], "clicked")
self.Button2.set_sensitive(True)
# make sure any locks on db are released:
self.db.rollback()
def get_vbox(self):
"""returns the vbox of this thread"""
return self.mainVBox

View File

@ -65,8 +65,8 @@ class Fulltilt(HandHistoryConverter):
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
#static regex for tourney purpose
@ -128,6 +128,7 @@ class Fulltilt(HandHistoryConverter):
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
logging.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostDead = re.compile(r"^%s posts a dead small blind of \$?(?P<SB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P<BB>[.0-9]+)" % player_re, re.MULTILINE)
self.re_Antes = re.compile(r"^%s antes \$?(?P<ANTE>[.0-9]+)" % player_re, re.MULTILINE)
self.re_BringIn = re.compile(r"^%s brings in for \$?(?P<BRINGIN>[.0-9]+)" % player_re, re.MULTILINE)
@ -298,6 +299,8 @@ class Fulltilt(HandHistoryConverter):
hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
for a in self.re_PostDead.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):

298
pyfpdb/GuiDatabase.py Executable file
View File

@ -0,0 +1,298 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#Copyright 2008 Carl Gherardi
#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 os
import sys
import traceback
import Queue
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import pango
import logging
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
log = logging.getLogger("maintdbs")
import Exceptions
import Database
class GuiDatabase:
COL_DBMS = 0
COL_NAME = 1
COL_DESC = 2
COL_USER = 3
COL_PASS = 4
COL_HOST = 5
COL_ICON = 6
def __init__(self, config, mainwin, dia):
self.config = config
self.main_window = mainwin
self.dia = dia
try:
#self.dia.set_modal(True)
self.vbox = self.dia.vbox
#gtk.Widget.set_size_request(self.vbox, 700, 400);
# list of databases in self.config.supported_databases:
self.liststore = gtk.ListStore(str, str, str, str
,str, str, str, str) #object, gtk.gdk.Pixbuf)
# dbms, name, comment, user, pass, ip, status(, icon?)
# this is how to add a filter:
#
# # Creation of the filter, from the model
# filter = self.liststore.filter_new()
# filter.set_visible_column(1)
#
# # The TreeView gets the filter as model
# self.listview = gtk.TreeView(filter)
self.listview = gtk.TreeView(model=self.liststore)
self.listview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_NONE)
self.listcols = []
scrolledwindow = gtk.ScrolledWindow()
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolledwindow.add(self.listview)
self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0)
refreshbutton = gtk.Button("Refresh")
refreshbutton.connect("clicked", self.refresh, None)
self.vbox.pack_start(refreshbutton, False, False, 3)
refreshbutton.show()
col = self.addTextColumn("Type", 0, False)
col = self.addTextColumn("Name", 1, False)
col = self.addTextColumn("Description", 2, True)
col = self.addTextColumn("Username", 3, True)
col = self.addTextColumn("Password", 4, True)
col = self.addTextColumn("Host", 5, True)
col = self.addTextObjColumn("", 6)
self.loadDbs()
self.dia.connect('response', self.dialog_response_cb)
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print 'guidbmaint: '+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
def dialog_response_cb(self, dialog, response_id):
# this is called whether close button is pressed or window is closed
dialog.destroy()
def get_dialog(self):
return self.dia
def addTextColumn(self, title, n, editable=False):
col = gtk.TreeViewColumn(title)
self.listview.append_column(col)
cRender = gtk.CellRendererText()
cRender.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
cRender.set_property('editable', editable)
cRender.connect('edited', self.edited_cb, (self.liststore,n))
col.pack_start(cRender, True)
col.add_attribute(cRender, 'text', n)
col.set_max_width(1000)
col.set_spacing(0) # no effect
self.listcols.append(col)
col.set_clickable(True)
col.connect("clicked", self.sortCols, n)
return(col)
def edited_cb(self, cell, path, new_text, user_data):
liststore, col = user_data
valid = True
name = self.liststore[path][self.COL_NAME]
# Validate new value (only for dbms so far, but dbms now not updateable so no validation at all!)
#if col == self.COL_DBMS:
# if new_text not in Configuration.DATABASE_TYPES:
# valid = False
if valid:
self.liststore[path][col] = new_text
self.config.set_db_parameters( db_server = self.liststore[path][self.COL_DBMS]
, db_name = name
, db_ip = self.liststore[path][self.COL_HOST]
, db_user = self.liststore[path][self.COL_USER]
, db_pass = self.liststore[path][self.COL_PASS] )
return
def check_new_name(self, path, new_text):
name_ok = True
for i,db in enumerate(self.liststore):
if i != path and new_text == db[self.COL_NAME]:
name_ok = False
#TODO: popup an error message telling user names must be unique
return name_ok
def addTextObjColumn(self, title, n):
col = gtk.TreeViewColumn(title)
self.listview.append_column(col)
cRenderT = gtk.CellRendererText()
cRenderT.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
col.pack_start(cRenderT, False)
col.add_attribute(cRenderT, 'text', n)
cRenderP = gtk.CellRendererPixbuf()
col.pack_start(cRenderP, False)
col.add_attribute(cRenderP, 'stock-id', n+1)
col.set_max_width(1000)
col.set_spacing(0) # no effect
self.listcols.append(col)
#col.set_clickable(True)
#col.connect("clicked", self.sortCols, p)
return(col)
def loadDbs(self):
self.liststore.clear()
self.listcols = []
self.dbs = [] # list of tuples: (dbms, name, comment, user, passwd, host, status, icon)
try:
# want to fill: dbms, name, comment, user, passwd, host, status(, icon?)
for name in self.config.supported_databases: #db_ip/db_user/db_pass/db_server
dbms = self.config.supported_databases[name].db_server # mysql/postgresql/sqlite
dbms_num = self.config.get_backend(dbms) # 2 / 3 / 4
comment = ""
if dbms == 'sqlite':
user = ""
passwd = ""
else:
user = self.config.supported_databases[name].db_user
passwd = self.config.supported_databases[name].db_pass
host = self.config.supported_databases[name].db_ip
status = ""
icon = None
err_msg = ""
db = Database.Database(self.config, sql = None, autoconnect = False)
# try to connect to db, set status and err_msg if it fails
try:
# is creating empty db for sqlite ... mod db.py further?
# add noDbTables flag to db.py?
db.connect(backend=dbms_num, host=host, database=name, user=user, password=passwd, create=False)
if db.connected:
status = 'ok'
icon = gtk.STOCK_APPLY
if db.wrongDbVersion:
status = 'old'
icon = gtk.STOCK_INFO
except Exceptions.FpdbMySQLAccessDenied:
err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?"
status = "failed"
icon = gtk.STOCK_CANCEL
except Exceptions.FpdbMySQLNoDatabase:
err_msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - " \
+ "Please check that the MySQL service has been started"
status = "failed"
icon = gtk.STOCK_CANCEL
except Exceptions.FpdbPostgresqlAccessDenied:
err_msg = "Postgres Server reports: Access denied. Are your permissions set correctly?"
status = "failed"
except Exceptions.FpdbPostgresqlNoDatabase:
err_msg = "Postgres client reports: Unable to connect - " \
+ "Please check that the Postgres service has been started"
status = "failed"
icon = gtk.STOCK_CANCEL
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
log.info( 'db connection to '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: '
+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) )
status = "failed"
icon = gtk.STOCK_CANCEL
b = gtk.Button(name)
b.show()
iter = self.liststore.append( (dbms, name, comment, user, passwd, host, status, icon) )
self.listview.show()
scrolledwindow.show()
self.vbox.show()
self.dia.set_focus(self.listview)
self.vbox.show_all()
self.dia.show()
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print 'loaddbs error: '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' \
+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
def sortCols(self, col, n):
try:
if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
col.set_sort_order(gtk.SORT_DESCENDING)
else:
col.set_sort_order(gtk.SORT_ASCENDING)
self.liststore.set_sort_column_id(n, col.get_sort_order())
#self.liststore.set_sort_func(n, self.sortnums, (n,grid))
for i in xrange(len(self.listcols)):
self.listcols[i].set_sort_indicator(False)
self.listcols[n].set_sort_indicator(True)
# use this listcols[col].set_sort_indicator(True)
# to turn indicator off for other cols
except:
err = traceback.extract_tb(sys.exc_info()[2])
print "***sortCols error: " + str(sys.exc_info()[1])
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
def refresh(self, widget, data):
self.loadDbs()
if __name__=="__main__":
config = Configuration.Config()
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
win.set_title("Test Log Viewer")
win.set_border_width(1)
win.set_default_size(600, 500)
win.set_resizable(True)
dia = gtk.Dialog("Log Viewer",
win,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
dia.set_default_size(500, 500)
log = GuiLogView(config, win, dia.vbox)
response = dia.run()
if response == gtk.RESPONSE_ACCEPT:
pass
dia.destroy()

View File

@ -92,7 +92,7 @@ class GuiPlayerStats (threading.Thread):
, ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line
, ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code)
, ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code)
, ["n", True, "Hds", 1.0, "%d", "str"]
, ["n", True, "Hds", 1.0, "%1.0f", "str"]
, ["avgseats", False, "Seats", 1.0, "%3.1f", "str"]
, ["vpip", True, "VPIP", 1.0, "%3.1f", "str"]
, ["pfr", True, "PFR", 1.0, "%3.1f", "str"]
@ -481,7 +481,23 @@ class GuiPlayerStats (threading.Thread):
else:
gametest = "and gt.category IS NULL"
query = query.replace("<game_test>", gametest)
sitetest = ""
q = []
for m in self.filters.display.items():
if m[0] == 'Sites' and m[1]:
for n in sitenos:
q.append(n)
if len(q) > 0:
sitetest = str(tuple(q))
sitetest = sitetest.replace("L", "")
sitetest = sitetest.replace(",)",")")
sitetest = sitetest.replace("u'","'")
sitetest = "and gt.siteId in %s" % sitetest
else:
sitetest = "and gt.siteId IS NULL"
query = query.replace("<site_test>", sitetest)
if seats:
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
if 'show' in seats and seats['show']:
@ -520,7 +536,7 @@ class GuiPlayerStats (threading.Thread):
blindtest = str(tuple(nolims))
blindtest = blindtest.replace("L", "")
blindtest = blindtest.replace(",)",")")
bbtest = bbtest + blindtest + ' ) ) )'
bbtest = bbtest + blindtest + ' ) )'
else:
bbtest = bbtest + '(-1) ) )'
if type == 'ring':
@ -539,7 +555,7 @@ class GuiPlayerStats (threading.Thread):
query = query.replace("<orderbyhgameTypeId>", "")
groupLevels = "show" not in str(limits)
if groupLevels:
query = query.replace("<hgameTypeId>", "p.name")
query = query.replace("<hgameTypeId>", "-1")
else:
query = query.replace("<hgameTypeId>", "h.gameTypeId")

View File

@ -61,7 +61,7 @@ import Hud
# get config and set up logger
c = Configuration.Config(file=options.config)
c = Configuration.Config(file=options.config, dbname=options.dbname)
log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt')
@ -78,14 +78,14 @@ class HUD_main(object):
try:
if not options.errorsToConsole:
fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt')
print "Note: error output is being diverted to:\n"+fileName \
+ "\nAny major error will be reported there _only_.\n"
log.info("Note: error output is being diverted to:"+fileName)
log.info("Any major error will be reported there _only_.")
errorFile = open(fileName, 'w', 0)
sys.stderr = errorFile
sys.stderr.write("HUD_main: starting ...\n")
fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt')
print "Note: error output is being diverted to:\n"+fileName \
+ "\nAny major error will be reported there _only_.\n"
log.info("Note: error output is being diverted to:"+fileName)
log.info("Any major error will be reported there _only_.")
errorFile = open(fileName, 'w', 0)
sys.stderr = errorFile
sys.stderr.write("HUD_main: starting ...\n")
self.hud_dict = {}
self.hud_params = self.config.get_hud_ui_parameters()
@ -237,7 +237,7 @@ class HUD_main(object):
try:
(table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \
self.db_connection.get_table_info(new_hand_id)
except Exception, err:
except Exception:
log.error("db error: skipping %s" % new_hand_id)
continue
t1 = time.time()

View File

@ -125,6 +125,7 @@ class Hand(object):
# currency symbol for this hand
self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
self.pot.setSym(self.sym)
self.is_duplicate = False # i.e. don't update hudcache if true
def __str__(self):
vars = ( ("BB", self.bb),
@ -236,6 +237,7 @@ db: a connected Database object"""
# TourneysPlayers
else:
log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo'])
self.is_duplicate = True # i.e. don't update hudcache
raise FpdbHandDuplicate(hh['siteHandNo'])
def updateHudCache(self, db):
@ -321,8 +323,10 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio
self.stacks[player] -= Decimal(ante)
act = (player, 'posts', "ante", ante, self.stacks[player]==0)
self.actions['BLINDSANTES'].append(act)
self.pot.addMoney(player, Decimal(ante))
# self.pot.addMoney(player, Decimal(ante))
self.pot.addCommonMoney(player, Decimal(ante))
#I think the antes should be common money, don't have enough hand history to check
def addBlind(self, player, blindtype, amount):
# if player is None, it's a missing small blind.
# The situation we need to cover are:
@ -340,14 +344,16 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio
self.actions['BLINDSANTES'].append(act)
if blindtype == 'both':
amount = self.bb
# work with the real ammount. limit games are listed as $1, $2, where
# the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2....
amount = self.bb
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb))
self.pot.addCommonMoney(player, Decimal(self.sb))
if blindtype == 'secondsb':
amount = Decimal(0)
self.bets['BLINDSANTES'][player].append(Decimal(self.sb))
self.pot.addCommonMoney(Decimal(self.sb))
self.pot.addCommonMoney(player, Decimal(self.sb))
self.bets['PREFLOP'][player].append(Decimal(amount))
self.pot.addMoney(player, Decimal(amount))
@ -509,10 +515,7 @@ Card ranks will be uppercased
self.totalcollected = 0;
#self.collected looks like [[p1,amount][px,amount]]
for entry in self.collected:
self.totalcollected += Decimal(entry[1])
self.totalcollected += Decimal(entry[1])
def getGameTypeAsString(self):
"""\
@ -674,6 +677,7 @@ class HoldemOmahaHand(Hand):
if self.maxseats is None:
self.maxseats = hhc.guessMaxSeats(self)
hhc.readOther(self)
#print "\nHand:\n"+str(self)
elif builtFrom == "DB":
if handid is not None:
self.select(handid) # Will need a handId
@ -991,11 +995,12 @@ class DrawHand(Hand):
self.lastBet['DEAL'] = Decimal(amount)
elif blindtype == 'both':
# extra small blind is 'dead'
self.lastBet['DEAL'] = Decimal(self.bb)
amount = Decimal(amount)/3
amount += amount
self.lastBet['DEAL'] = Decimal(amount)
self.posted = self.posted + [[player,blindtype]]
#print "DEBUG: self.posted: %s" %(self.posted)
def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False):
if player == self.hero: # we have hero's cards just update shown/mucked
if shown: self.shown.add(player)
@ -1410,7 +1415,7 @@ class Pot(object):
self.contenders = set()
self.committed = {}
self.streettotals = {}
self.common = Decimal(0)
self.common = {}
self.total = None
self.returned = {}
self.sym = u'$' # this is the default currency symbol
@ -1420,13 +1425,14 @@ class Pot(object):
def addPlayer(self,player):
self.committed[player] = Decimal(0)
self.common[player] = Decimal(0)
def addFold(self, player):
# addFold must be called when a player folds
self.contenders.discard(player)
def addCommonMoney(self, amount):
self.common += amount
def addCommonMoney(self, player, amount):
self.common[player] += amount
def addMoney(self, player, amount):
# addMoney must be called for any actions that put money in the pot, in the order they occur
@ -1434,7 +1440,7 @@ class Pot(object):
self.committed[player] += amount
def markTotal(self, street):
self.streettotals[street] = sum(self.committed.values()) + self.common
self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values())
def getTotalAtStreet(self, street):
if street in self.streettotals:
@ -1442,11 +1448,11 @@ class Pot(object):
return 0
def end(self):
self.total = sum(self.committed.values()) + self.common
self.total = sum(self.committed.values()) + sum(self.common.values())
# Return any uncalled bet.
committed = sorted([ (v,k) for (k,v) in self.committed.items()])
print "DEBUG: committed: %s" % committed
#print "DEBUG: committed: %s" % committed
#ERROR below. lastbet is correct in most cases, but wrong when
# additional money is committed to the pot in cash games
# due to an additional sb being posted. (Speculate that

View File

@ -69,6 +69,7 @@ out_path (default '-' = sys.stdout)
follow : whether to tail -f the input"""
self.config = config
self.import_parameters = self.config.get_import_parameters()
#log = Configuration.get_logger("logging.conf", "parser", log_dir=self.config.dir_log)
log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) )
@ -87,12 +88,8 @@ follow : whether to tail -f the input"""
if in_path == '-':
self.in_fh = sys.stdin
self.out_fh = get_out_fh(out_path, self.import_parameters)
if out_path == '-':
self.out_fh = sys.stdout
else:
# TODO: out_path should be sanity checked.
self.out_fh = sys.stdout
self.follow = follow
self.compiledPlayers = set()
self.maxseats = 10
@ -446,8 +443,8 @@ or None if we fail to get the info """
def guessMaxSeats(self, hand):
"""Return a guess at maxseats when not specified in HH."""
# if some other code prior to this has already set it, return it
if maxseats > 1 and maxseats < 11:
return maxseats
if self.maxseats > 1 and self.maxseats < 11:
return self.maxseats
mo = self.maxOccSeat(hand)
if mo == 10: return 10 #that was easy
@ -515,3 +512,23 @@ def getSiteHhc(config, sitename):
hhcName = config.supported_sites[sitename].converter
hhcModule = __import__(hhcName)
return getattr(hhcModule, hhcName[:-6])
def get_out_fh(out_path, parameters):
if out_path == '-':
return(sys.stdout)
elif parameters['saveStarsHH']:
out_dir = os.path.dirname(out_path)
if not os.path.isdir(out_dir) and out_dir != '':
try:
os.makedirs(out_dir)
except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D
log.error("Unable to create output directory %s for HHC!" % out_dir)
print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir
else:
log.info("Created directory '%s'" % out_dir)
try:
return(codecs.open(out_path, 'w', 'utf8'))
except:
log.error("out_path %s couldn't be opened" % (out_path))
else:
return(sys.stdout)

View File

@ -27,7 +27,7 @@ def fpdb_options():
action="store_true",
help="If passed error output will go to the console rather than .")
parser.add_option("-d", "--databaseName",
dest="dbname", default="fpdb",
dest="dbname",
help="Overrides the default database name")
parser.add_option("-c", "--configFile",
dest="config", default=None,

View File

@ -77,14 +77,15 @@ class PartyPoker(HandHistoryConverter):
re.VERBOSE)
re_HandInfo = re.compile("""
^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)\s+
^Table\s+(?P<TTYPE>[$a-zA-Z0-9 ]+)?\s+
(?: \#|\(|)(?P<TABLE>\d+)\)?\s+
(?:[a-zA-Z0-9 ]+\s+\#(?P<MTTTABLE>\d+).+)?
(\(No\sDP\)\s)?
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
\s+Total\s+number\s+of\s+players\s+\:\s+(?P<PLYRS>\d+)/?(?P<MAX>\d+)?
""",
re.VERBOSE|re.MULTILINE)
re.VERBOSE|re.MULTILINE|re.DOTALL)
re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE)
re_SplitHands = re.compile('\x00+')
@ -106,7 +107,6 @@ class PartyPoker(HandHistoryConverter):
def guessMaxSeats(self, hand):
"""Return a guess at max_seats when not specified in HH."""
mo = self.maxOccSeat(hand)
if mo == 10: return mo
if mo == 2: return 2
if mo <= 6: return 6
@ -260,6 +260,7 @@ class PartyPoker(HandHistoryConverter):
for i,v in enumerate(self.collected):
if v[0] in self.pot.returned:
self.collected[i][1] = Decimal(v[1]) - self.pot.returned[v[0]]
self.collectees[v[0]] -= self.pot.returned[v[0]]
return origTotalPot()
return totalPot
instancemethod = type(hand.totalPot)
@ -313,6 +314,9 @@ class PartyPoker(HandHistoryConverter):
if key == 'PLAY' and info['PLAY'] != 'Real':
# if realy party doesn's save play money hh
hand.gametype['currency'] = 'play'
if key == 'MAX' and info[key] is not None:
hand.maxseats = int(info[key])
def readButton(self, hand):
m = self.re_Button.search(hand.handText)
@ -465,8 +469,9 @@ class PartyPoker(HandHistoryConverter):
def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
"Returns string to search in windows titles"
if type=="tour":
print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (table_name, table_number)
return "%s.+Table\s#%s" % (table_name, table_number)
TableName = table_name.split(" ")
print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number)
return "%s.+Table\s#%s" % (TableName[0], table_number)
else:
print 'party', 'getTableTitleRe', table_number
return table_name

30
pyfpdb/PokerStarsToFpdb.py Executable file → Normal file
View File

@ -140,6 +140,14 @@ class PokerStars(HandHistoryConverter):
mg = m.groupdict()
# translations from captured groups to fpdb info strings
Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'),
'0.50': ('0.10', '0.25'), '1.00': ('0.25', '0.50'), '2.00': ('0.50', '1.00'),
'4.00': ('1.00', '2.00'), '6.00': ('1.00', '3.00'), '10.00': ('2.00', '5.00'),
'20.00': ('5.00', '10.00'), '30.00': ('10.00', '15.00'), '60.00': ('15.00', '30.00'),
'100.00': ('25.00', '50.00'),'200.00': ('50.00', '100.00'),'400.00': ('100.00', '200.00'),
'1000.00': ('250.00', '500.00')}
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' }
games = { # base, category
"Hold'em" : ('hold','holdem'),
@ -173,6 +181,10 @@ class PokerStars(HandHistoryConverter):
else:
info['type'] = 'tour'
if info['limitType'] == 'fl' and info['bb'] is not None and info['type'] == 'ring' and info['base'] != 'stud':
info['sb'] = Lim_Blinds[mg['BB']][0]
info['bb'] = Lim_Blinds[mg['BB']][1]
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
return info
@ -287,16 +299,14 @@ class PokerStars(HandHistoryConverter):
hand.addBringIn(m.group('PNAME'), m.group('BRINGIN'))
def readBlinds(self, hand):
try:
count = 0
for a in self.re_PostSB.finditer(hand.handText):
if count == 0:
hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB'))
count = 1
else:
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
except: # no small blind
hand.addBlind(None, None, None)
liveBlind = True
for a in self.re_PostSB.finditer(hand.handText):
if liveBlind:
hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB'))
liveBlind = False
else:
# Post dead blinds as ante
hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB'))
for a in self.re_PostBB.finditer(hand.handText):
hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB'))
for a in self.re_PostBoth.finditer(hand.handText):

View File

@ -1346,6 +1346,7 @@ class Sql:
# same as above except stats are aggregated for all blind/limit levels
self.query['get_stats_from_hand_aggregated'] = """
/* explain query plan */
SELECT hc.playerId AS player_id,
max(case when hc.gametypeId = h.gametypeId
then hp.seatNo
@ -1915,6 +1916,7 @@ class Sql:
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
<game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
@ -1999,6 +2001,7 @@ class Sql:
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
<game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
@ -2076,8 +2079,7 @@ class Sql:
,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr
,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr
,avg(h.seats+0.0) AS avgseats
/*,variance(hp.totalProfit/100.0) AS variance*/
,0.0 AS variance
,variance(hp.totalProfit/100.0) AS variance
from HandsPlayers hp
inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId)
@ -2085,6 +2087,7 @@ class Sql:
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
<game_test>
<site_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>

View File

@ -32,12 +32,16 @@ import gtk
import gobject
# fpdb/free poker tools modules
import Configuration
from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os
if os.name == 'posix':
import XTables as Tables
elif os.name == 'nt':
import WinTables as Tables
config = Configuration.Config()
# Main function used for testing
if __name__=="__main__":
# c = Configuration.Config()
@ -82,11 +86,16 @@ if __name__=="__main__":
(tour_no, tab_no) = table_name.split(",", 1)
tour_no = tour_no.rstrip()
tab_no = tab_no.rstrip()
table = Tables.Table(None, tournament = tour_no, table_number = tab_no)
type = "tour"
table_kwargs = dict(tournament = tour_no, table_number = tab_no)
else: # not a tournament
print "cash game"
table_name = table_name.rstrip()
table = Tables.Table(None, table_name = table_name)
type = "cash"
table_kwargs = dict(table_name = table_name)
search_string = getTableTitleRe(config, "Full Tilt Poker", type, **table_kwargs)
table = Tables.Table(search_string, **table_kwargs)
table.gdk_handle = gtk.gdk.window_foreign_new(table.number)
print "table =", table

View File

@ -68,7 +68,7 @@ class Table(Table_Window):
window_number = None
for listing in os.popen('xwininfo -root -tree').readlines():
if re.search(search_string, listing):
print listing
# print listing
mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing)
self.number = int( mo.group(1), 0)
self.width = int( mo.group(4) )
@ -89,7 +89,6 @@ class Table(Table_Window):
# break
if window_number is None:
print "Window %s not found. Skipping." % search_string
return None
# my_geo = self.window.get_geometry()

View File

@ -97,6 +97,7 @@ except:
import GuiPrefs
import GuiLogView
import GuiDatabase
import GuiBulkImport
import GuiPlayerStats
import GuiPositionalStats
@ -288,10 +289,31 @@ class fpdb:
dia.destroy()
def dia_create_del_database(self, widget, data=None):
self.warning_box("Unimplemented: Create/Delete Database")
self.obtain_global_lock()
self.release_global_lock()
def dia_maintain_dbs(self, widget, data=None):
self.warning_box("Unimplemented: Maintain Databases")
return
if len(self.tab_names) == 1:
if self.obtain_global_lock(): # returns true if successful
# only main tab has been opened, open dialog
dia = gtk.Dialog("Maintain Databases",
self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
dia.set_default_size(700, 320)
prefs = GuiDatabase.GuiDatabase(self.config, self.window, dia)
response = dia.run()
if response == gtk.RESPONSE_ACCEPT:
# save updated config
self.config.save()
self.release_global_lock()
dia.destroy()
else:
self.warning_box("Cannot open Database Maintenance window because "
+ "other windows have been opened. Re-start fpdb to use this option.")
def dia_create_del_user(self, widget, data=None):
self.warning_box("Unimplemented: Create/Delete user")
@ -620,7 +642,7 @@ class fpdb:
<menuitem action="tableviewer"/>
</menu>
<menu action="database">
<menuitem action="createdb"/>
<menuitem action="maintaindbs"/>
<menuitem action="createuser"/>
<menuitem action="createtabs"/>
<menuitem action="rebuildhudcache"/>
@ -663,7 +685,7 @@ class fpdb:
('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented),
('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer),
('database', None, '_Database'),
('createdb', None, 'Create or Delete _Database (todo)', None, 'Create or Delete Database', self.dia_create_del_database),
('maintaindbs', None, '_Maintain Databases (todo)', None, 'Maintain Databases', self.dia_maintain_dbs),
('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),
('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache),
@ -685,9 +707,15 @@ class fpdb:
window.add_accel_group(accel_group)
return menubar
def load_profile(self):
def load_profile(self, create_db = False):
"""Loads profile from the provided path name."""
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
if self.config.file_error:
self.warning_box( "There is an error in your config file\n" + self.config.file
+ "\n\nError is: " + str(self.config.file_error)
, diatitle="CONFIG FILE ERROR" )
exit()
log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log)
print "Logfile is " + os.path.join(self.config.dir_log, self.config.log_file) + "\n"
if self.config.example_copy:
@ -905,7 +933,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.tab_main_help(None, None)
self.window.show()
self.load_profile()
self.load_profile(create_db = True)
if not options.errorsToConsole:
fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt')
@ -997,23 +1025,24 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
return response
def validate_config(self):
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase)
#hhdir = os.path.join(hhbase,site)
hhdir = hhbase
if not os.path.isdir(hhdir):
diapath = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Setup hh dir")
diastring = "WARNING: Unable to find output hh directory %s\n\n Press YES to create this directory, or NO to select a new one." % hhdir
diapath.format_secondary_text(diastring)
response = diapath.run()
diapath.destroy()
if response == gtk.RESPONSE_YES:
try:
os.makedirs(hhdir)
except:
self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.")
elif response == gtk.RESPONSE_NO:
self.select_hhArchiveBase()
if self.config.get_import_parameters().get('saveStarsHH'):
hhbase = self.config.get_import_parameters().get("hhArchiveBase")
hhbase = os.path.expanduser(hhbase)
#hhdir = os.path.join(hhbase,site)
hhdir = hhbase
if not os.path.isdir(hhdir):
diapath = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Setup hh dir")
diastring = "WARNING: Unable to find output hh directory %s\n\n Press YES to create this directory, or NO to select a new one." % hhdir
diapath.format_secondary_text(diastring)
response = diapath.run()
diapath.destroy()
if response == gtk.RESPONSE_YES:
try:
os.makedirs(hhdir)
except:
self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.")
elif response == gtk.RESPONSE_NO:
self.select_hhArchiveBase()
def select_hhArchiveBase(self, widget=None):
fc = gtk.FileChooserDialog(title="Select HH Output Directory", parent=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_OPEN,gtk.RESPONSE_OK), backend=None)

View File

@ -456,7 +456,7 @@ class Importer:
# FIXME: Need to test for bulk import that isn't rebuilding the cache
if self.callHud:
for hand in handlist:
if hand is not None:
if hand is not None and not hand.is_duplicate:
hand.updateHudCache(self.database)
self.database.commit()

View File

@ -73,6 +73,7 @@ from distutils.core import setup
import py2exe
import glob
import matplotlib
import shutil
from datetime import date
@ -111,7 +112,7 @@ def test_and_remove(top):
# remove build and dist dirs if they exist
test_and_remove('dist')
test_and_remove('build')
test_and_remove('gfx')
#test_and_remove('gfx')
today = date.today().strftime('%Y%m%d')
@ -151,7 +152,7 @@ setup(
},
# files in 2nd value in tuple are moved to dir named in 1st value
data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf'])
data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf', '../docs/readme.txt'])
,(dist_dir, [r'..\run_fpdb.bat'])
,( dist_dir + r'\gfx', glob.glob(r'..\gfx\*.*') )
# line below has problem with fonts subdir ('not a regular file')
@ -174,3 +175,36 @@ dest = dest.replace('\\', '\\\\')
os.rename( 'pyfpdb', dest )
print "Enter directory name for GTK 2.14 (e.g. c:\code\gtk_2.14.7-20090119)\n: ", # the comma means no newline
gtk_dir = sys.stdin.readline().rstrip()
print "\ncopying files and dirs from ", gtk_dir, "to", dest.replace('\\\\', '\\'), "..."
src = os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll')
src = src.replace('\\', '\\\\')
shutil.copy( src, dest )
src = os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll')
src = src.replace('\\', '\\\\')
shutil.copy( src, dest )
src_dir = os.path.join(gtk_dir, 'etc')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'etc')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )
src_dir = os.path.join(gtk_dir, 'lib')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'lib')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )
src_dir = os.path.join(gtk_dir, 'share')
src_dir = src_dir.replace('\\', '\\\\')
dest_dir = os.path.join(dest, 'share')
dest_dir = dest_dir.replace('\\', '\\\\')
shutil.copytree( src_dir, dest_dir )

View File

@ -1,7 +1,7 @@
rem .bat script to run fpdb
cd pyfpdb
fpdb.exe
rem .bat script to run fpdb
cd pyfpdb
fpdb.exe

0
run_fpdb.py Executable file → Normal file
View File