pulled and merged from fpdboz

This commit is contained in:
sqlcoder 2008-12-07 23:38:33 +00:00
commit 4dc15bfd94
30 changed files with 7474 additions and 5957 deletions

109
pyfpdb/CarbonToFpdb.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python
# Copyright 2008, Carl Gherardi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
# Standard Library modules
import Configuration
import traceback
import sys
import re
import xml.dom.minidom
from xml.dom.minidom import Node
from HandHistoryConverter import HandHistoryConverter
# Carbon format looks like:
# 1) <description type="Holdem" stakes="No Limit ($0.25/$0.50)"/>
# 2) <game id="14902583-5578" starttime="20081006145401" numholecards="2" gametype="2" realmoney="true" data="20081006|Niagara Falls (14902583)|14902583|14902583-5578|false">
# 3) <players dealer="8">
# <player seat="3" nickname="PlayerInSeat3" balance="$43.29" dealtin="true" />
# ...
# 4) <round id="BLINDS" sequence="1">
# <event sequence="1" type="SMALL_BLIND" player="0" amount="0.25"/>
# <event sequence="2" type="BIG_BLIND" player="1" amount="0.50"/>
# 5) <round id="PREFLOP" sequence="2">
# <event sequence="3" type="CALL" player="2" amount="0.50"/>
# 6) <round id="POSTFLOP" sequence="3">
# <event sequence="16" type="BET" player="3" amount="1.00"/>
# ....
# <cards type="COMMUNITY" cards="7d,Jd,Jh"/>
# The full sequence for a NHLE cash game is:
# BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME
# This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_GAME
class CarbonPoker(HandHistoryConverter):
def __init__(self, config, filename):
print "Initialising Carbon Poker converter class"
HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init
self.setFileType("xml")
def readSupportedGames(self):
pass
def determineGameType(self):
gametype = []
desc_node = self.doc.getElementsByTagName("description")
#TODO: no examples of non ring type yet
gametype = gametype + ["ring"]
type = desc_node[0].getAttribute("type")
if(type == "Holdem"):
gametype = gametype + ["hold"]
else:
print "Unknown gametype: '%s'" % (type)
stakes = desc_node[0].getAttribute("stakes")
#TODO: no examples of anything except nlhe
m = re.match('(?P<LIMIT>No Limit)\s\(\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\)', stakes)
if(m.group('LIMIT') == "No Limit"):
gametype = gametype + ["nl"]
gametype = gametype + [self.float2int(m.group('SB'))]
gametype = gametype + [self.float2int(m.group('BB'))]
return gametype
def readPlayerStacks(self):
pass
def readBlinds(self):
pass
def readAction(self):
pass
# Override read function as xml.minidom barfs on the Carbon layout
# This is pretty dodgy
def readFile(self, filename):
print "Carbon: Reading file: '%s'" %(filename)
infile=open(filename, "rU")
self.obs = infile.read()
infile.close()
self.obs = "<CarbonHHFile>\n" + self.obs + "</CarbonHHFile>"
try:
doc = xml.dom.minidom.parseString(self.obs)
self.doc = doc
except:
traceback.print_exc(file=sys.stderr)
if __name__ == "__main__":
c = Configuration.Config()
e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml")
e.processFile()
print str(e)

View File

@ -53,7 +53,7 @@ if __name__ == "__main__":
(options, sys.argv) = parser.parse_args() (options, sys.argv) = parser.parse_args()
settings={'imp-callFpdbHud':False, 'db-backend':2} settings={'callFpdbHud':False, 'db-backend':2}
settings['db-host']=options.server settings['db-host']=options.server
settings['db-user']=options.user settings['db-user']=options.user
settings['db-password']=options.password settings['db-password']=options.password

View File

@ -114,6 +114,7 @@ class Game:
stat.popup = stat_node.getAttribute("popup") stat.popup = stat_node.getAttribute("popup")
stat.hudprefix = stat_node.getAttribute("hudprefix") stat.hudprefix = stat_node.getAttribute("hudprefix")
stat.hudsuffix = stat_node.getAttribute("hudsuffix") stat.hudsuffix = stat_node.getAttribute("hudsuffix")
stat.hudcolor = stat_node.getAttribute("hudcolor")
self.stats[stat.stat_name] = stat self.stats[stat.stat_name] = stat
@ -185,7 +186,7 @@ class Import:
def __init__(self, node): def __init__(self, node):
self.interval = node.getAttribute("interval") self.interval = node.getAttribute("interval")
self.callFpdbHud = node.getAttribute("callFpdbHud") self.callFpdbHud = node.getAttribute("callFpdbHud")
self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.hhArchiveBase = node.getAttribute("hhArchiveBase")
def __str__(self): def __str__(self):
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s" % (self.interval, self.callFpdbHud, self.hhArchiveBase) return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s" % (self.interval, self.callFpdbHud, self.hhArchiveBase)
@ -443,9 +444,9 @@ class Config:
def get_import_parameters(self): def get_import_parameters(self):
imp = {} imp = {}
try: try:
imp['callFpdbHud'] = self.callFpdbHud imp['callFpdbHud'] = self.imp.callFpdbHud
imp['interval'] = self.interval imp['interval'] = self.imp.interval
imp['hhArchiveBase'] = self.hhArchiveBase imp['hhArchiveBase'] = self.imp.hhArchiveBase
except: # Default params except: # Default params
imp['callFpdbHud'] = True imp['callFpdbHud'] = True
imp['interval'] = 10 imp['interval'] = 10
@ -612,9 +613,7 @@ if __name__== "__main__":
print "----------- END POPUP WINDOW FORMATS -----------" print "----------- END POPUP WINDOW FORMATS -----------"
print "\n----------- IMPORT -----------" print "\n----------- IMPORT -----------"
tmp = c.get_import_parameters() print c.imp
for param in tmp:
print " " + str(param) + ": " + str(tmp[param])
print "----------- END IMPORT -----------" print "----------- END IMPORT -----------"
print "\n----------- TABLE VIEW -----------" print "\n----------- TABLE VIEW -----------"

View File

@ -142,12 +142,27 @@ class Database:
cards[s_dict['seat_number']] = s_dict cards[s_dict['seat_number']] = s_dict
return (cards) return (cards)
def get_stats_from_hand(self, hand, player_id = False): def get_action_from_hand(self, hand_no):
action = [ [], [], [], [], [] ]
c = self.connection.cursor()
c.execute(self.sql.query['get_action_from_hand'], (hand_no))
for row in c.fetchall():
street = row[0]
act = row[1:]
action[street].append(act)
return action
def get_stats_from_hand(self, hand, aggregate = False):
c = self.connection.cursor() c = self.connection.cursor()
if not player_id: player_id = "%" if aggregate:
query = 'get_stats_from_hand'
subs = (hand, hand)
else:
query = 'get_stats_from_hand_aggregated'
subs = (hand, hand, hand)
# get the players in the hand and their seats # get the players in the hand and their seats
# c.execute(self.sql.query['get_players_from_hand'], (hand, player_id))
c.execute(self.sql.query['get_players_from_hand'], (hand, )) c.execute(self.sql.query['get_players_from_hand'], (hand, ))
names = {} names = {}
seats = {} seats = {}
@ -156,8 +171,7 @@ class Database:
seats[row[0]] = row[1] seats[row[0]] = row[1]
# now get the stats # now get the stats
# c.execute(self.sql.query['get_stats_from_hand'], (hand, hand, player_id)) c.execute(self.sql.query[query], subs)
c.execute(self.sql.query['get_stats_from_hand'], (hand, hand))
colnames = [desc[0] for desc in c.description] colnames = [desc[0] for desc in c.description]
stat_dict = {} stat_dict = {}
for row in c.fetchall(): for row in c.fetchall():

176
pyfpdb/EverleafToFpdb.py Executable file
View File

@ -0,0 +1,176 @@
#!/usr/bin/env python
# Copyright 2008, Carl Gherardi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
########################################################################
import sys
import Configuration
from HandHistoryConverter import *
# Everleaf HH format
# Everleaf Gaming Game #55208539
# ***** Hand history for game #55208539 *****
# Blinds $0.50/$1 NL Hold'em - 2008/09/01 - 13:35:01
# Table Speed Kuala
# Seat 1 is the button
# Total number of players: 9
# Seat 1: BadBeatBox ( $ 98.97 USD )
# Seat 3: EricBlade ( $ 73.96 USD )
# Seat 4: randy888 ( $ 196.50 USD )
# Seat 5: BaronSengir ( $ 182.80 USD )
# Seat 6: dogge ( $ 186.06 USD )
# Seat 7: wings ( $ 50 USD )
# Seat 8: schoffeltje ( $ 282.05 USD )
# Seat 9: harrydebeng ( $ 109.45 USD )
# Seat 10: smaragdar ( $ 96.50 USD )
# EricBlade: posts small blind [$ 0.50 USD]
# randy888: posts big blind [$ 1 USD]
# wings: posts big blind [$ 1 USD]
# ** Dealing down cards **
# Dealt to EricBlade [ qc, 3c ]
# BaronSengir folds
# dogge folds
# wings raises [$ 2.50 USD]
# schoffeltje folds
# harrydebeng calls [$ 3.50 USD]
# smaragdar raises [$ 15.50 USD]
# BadBeatBox folds
# EricBlade folds
# randy888 folds
# wings calls [$ 12 USD]
# harrydebeng folds
# ** Dealing Flop ** [ qs, 3d, 8h ]
# wings: bets [$ 34.50 USD]
# smaragdar calls [$ 34.50 USD]
# ** Dealing Turn ** [ 2d ]
# ** Dealing River ** [ 6c ]
# dogge shows [ 9h, 9c ]a pair of nines
# spicybum shows [ 5d, 6d ]a straight, eight high
# harrydebeng does not show cards
# smaragdar wins $ 102 USD from main pot with a pair of aces [ ad, ah, qs, 8h, 6c ]
class Everleaf(HandHistoryConverter):
def __init__(self, config, file):
print "Initialising Everleaf converter class"
HandHistoryConverter.__init__(self, config, file, "Everleaf") # Call super class init.
self.sitename = "Everleaf"
self.setFileType("text")
self.rexx.setGameInfoRegex('.*Blinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)')
self.rexx.setSplitHandRegex('\n\n\n\n')
self.rexx.setHandInfoRegex('.*#(?P<HID>[0-9]+)\n.*\nBlinds \$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+) (?P<GAMETYPE>.*) - (?P<YEAR>[0-9]+)/(?P<MON>[0-9]+)/(?P<DAY>[0-9]+) - (?P<HR>[0-9]+):(?P<MIN>[0-9]+):(?P<SEC>[0-9]+)\nTable (?P<TABLE>[ a-zA-Z]+)\nSeat (?P<BUTTON>[0-9]+)')
self.rexx.setPlayerInfoRegex('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \( \$ (?P<CASH>[.0-9]+) USD \)')
self.rexx.setPostSbRegex('.*\n(?P<PNAME>.*): posts small blind \[\$? (?P<SB>[.0-9]+)')
self.rexx.setPostBbRegex('.*\n(?P<PNAME>.*): posts big blind \[\$? (?P<BB>[.0-9]+)')
# mct : what about posting small & big blinds simultaneously?
self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P<PNAME>.*)\s\[ (?P<HOLE1>\S\S), (?P<HOLE2>\S\S) \]')
self.rexx.setActionStepRegex('.*\n(?P<PNAME>.*) (?P<ATYPE>bets|checks|raises|calls|folds)(\s\[\$ (?P<BET>[.\d]+) USD\])?')
self.rexx.compileRegexes()
def readSupportedGames(self):
pass
def determineGameType(self):
# Cheating with this regex, only support nlhe at the moment
gametype = ["ring", "hold", "nl"]
m = self.rexx.game_info_re.search(self.obs)
gametype = gametype + [m.group('SB')]
gametype = gametype + [m.group('BB')]
return gametype
def readHandInfo(self, hand):
m = self.rexx.hand_info_re.search(hand.string)
hand.handid = m.group('HID')
hand.tablename = m.group('TABLE')
# These work, but the info is already in the Hand class - should be used for tourneys though.
# m.group('SB')
# m.group('BB')
# m.group('GAMETYPE')
# Believe Everleaf time is GMT/UTC, no transation necessary
# Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET]
# or : 2008/11/07 12:38:49 ET
# Not getting it in my HH files yet, so using
# 2008/11/10 3:58:52 ET
#TODO: Do conversion from GMT to ET
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
hand.starttime = "%d/%02d/%02d %d:%02d:%02d ET" %(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')),
int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand):
m = self.rexx.player_info_re.finditer(hand.string)
players = []
for a in m:
hand.addPlayer(a.group('SEAT'), a.group('PNAME'), a.group('CASH'))
def markStreets(self, hand):
# PREFLOP = ** Dealing down cards **
m = re.search('(\*\* Dealing down cards \*\*\n)(?P<PREFLOP>.*?\n\*\*)?( Dealing Flop \*\* \[ (?P<FLOP1>\S\S), (?P<FLOP2>\S\S), (?P<FLOP3>\S\S) \])?(?P<FLOP>.*?\*\*)?( Dealing Turn \*\* \[ (?P<TURN1>\S\S) \])?(?P<TURN>.*?\*\*)?( Dealing River \*\* \[ (?P<RIVER1>\S\S) \])?(?P<RIVER>.*)', hand.string,re.DOTALL)
# for street in m.groupdict():
# print "DEBUG: Street: %s\tspan: %s" %(street, str(m.span(street)))
hand.streets = m
def readBlinds(self, hand):
try:
m = self.rexx.small_blind_re.search(hand.string)
hand.addBlind(m.group('PNAME'), m.group('SB'))
#hand.posted = [m.group('PNAME')]
except:
hand.addBlind(None, 0)
#hand.posted = ["FpdbNBP"]
m = self.rexx.big_blind_re.finditer(hand.string)
for a in m:
hand.addBlind(a.group('PNAME'), a.group('BB'))
#hand.posted = hand.posted + [a.group('PNAME')]
def readHeroCards(self, hand):
m = self.rexx.hero_cards_re.search(hand.string)
if(m == None):
#Not involved in hand
hand.involved = False
else:
hand.hero = m.group('PNAME')
hand.addHoleCards(m.group('HOLE1'), m.group('HOLE2'))
def readAction(self, hand, street):
m = self.rexx.action_re.finditer(hand.streets.group(street))
hand.actions[street] = []
for action in m:
if action.group('ATYPE') == 'raises':
hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'calls':
hand.addCall( street, action.group('PNAME'), action.group('BET') )
elif action.group('ATYPE') == 'bets':
hand.addBet( street, action.group('PNAME'), action.group('BET') )
else:
print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),)
hand.actions[street] += [[action.group('PNAME'), action.group('ATYPE')]]
def getRake(self, hand):
hand.rake = hand.totalpot * Decimal('0.05') # probably not quite right
if __name__ == "__main__":
c = Configuration.Config()
e = Everleaf(c, "Speed_Kuala.txt")
e.processFile()
print str(e)

161
pyfpdb/FpdbRegex.py Normal file
View File

@ -0,0 +1,161 @@
# pokerstars_cash.py
# -*- coding: iso-8859-15
#
# PokerStats, an online poker statistics tracking software for Linux
# Copyright (C) 2007-2008 Mika Boström <bostik@iki.fi>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# Modified for use in fpdb by Carl Gherardi
import re
# These are PokerStars specific;
# More importantly, they are currently valid for cash game only.
#####
# XXX: There was a weird problem with saved hand histories in PokerStars
# client 2.491; if a user was present on the table (and thus anywhere in
# the hand history), with non-standard characters in their username, the
# client would prepend a literal Ctrl-P (ASCII 16, 0x10) character to
# the hand history title line. Hence, to allow these strangely saved
# hands to be parsed and imported, there is a conditional "one extra
# character" allowed at the start of the new hand regex.
class FpdbRegex:
def __init__(self):
self.__GAME_INFO_REGEX=''
self.__SPLIT_HAND_REGEX='\n\n\n'
self.__NEW_HAND_REGEX='^.?PokerStars Game #\d+:\s+Hold\'em'
self.__HAND_INFO_REGEX='^.*#(\d+):\s+(\S+)\s([\s\S]+)\s\(\$?([.0-9]+)/\$?([.0-9]+)\)\s-\s(\S+)\s-?\s?(\S+)\s\(?(\w+)\)?'
self.__TABLE_INFO_REGEX='^\S+\s+\'.*\'\s+(\d+)-max\s+Seat\s#(\d+)'
self.__PLAYER_INFO_REGEX='^Seat\s(\d+):\s(.*)\s\(\$?([.\d]+)\s'
self.__POST_SB_REGEX='^(.*):\sposts small blind'
self.__POST_BB_REGEX='^(.*):\sposts big blind'
self.__POST_BOTH_REGEX='^(.*):\sposts small & big blinds'
self.__HAND_STAGE_REGEX='^\*{3}\s(.*)\s\*{3}'
self.__HOLE_CARD_REGEX='^\*{3}\sHOLE CARDS'
self.__FLOP_CARD_REGEX='^\*{3}\sFLOP\s\*{3}\s\[(\S{2})\s(\S{2})\s(\S{2})\]'
self.__TURN_CARD_REGEX='^\*{3}\sTURN\s\*{3}\s\[\S{2}\s\S{2}\s\S{2}\]\s\[(\S{2})\]'
self.__RIVER_CARD_REGEX='^\*{3}\sRIVER\s\*{3}\s\[\S{2}\s\S{2}\s\S{2}\s\S{2}\]\s\[(\S{2})\]'
self.__SHOWDOWN_REGEX='^\*{3}\sSHOW DOWN'
self.__SUMMARY_REGEX='^\*{3}\sSUMMARY'
self.__UNCALLED_BET_REGEX='^Uncalled bet \(\$([.\d]+)\) returned to (.*)'
self.__POT_AND_RAKE_REGEX='^Total\spot\s\$([.\d]+).*\|\sRake\s\$([.\d]+)'
self.__COLLECT_POT_REGEX='^(.*)\scollected\s\$([.\d]+)\sfrom\s((main|side)\s)?pot'
self.__HERO_CARDS_REGEX='^Dealt\sto\s(.*)\s\[(\S{2})\s(\S{2})\]'
self.__SHOWN_CARDS_REGEX='^(.*):\sshows\s\[(\S{2})\s(\S{2})\]'
self.__ACTION_STEP_REGEX='^(.*):\s(bets|checks|raises|calls|folds)((\s\$([.\d]+))?(\sto\s\$([.\d]+))?)?'
self.__SHOWDOWN_ACTION_REGEX='^(.*):\s(shows|mucks)'
self.__SUMMARY_CARDS_REGEX='^Seat\s\d+:\s(.*)\s(showed|mucked)\s\[(\S{2})\s(\S{2})\]'
self.__SUMMARY_CARDS_EXTRA_REGEX='^Seat\s\d+:\s(.*)\s(\(.*\)\s)(showed|mucked)\s\[(\S{2})\s(\S{2})\]'
def compileRegexes(self):
### Compile the regexes
self.game_info_re = re.compile(self.__GAME_INFO_REGEX)
self.split_hand_re = re.compile(self.__SPLIT_HAND_REGEX)
self.hand_start_re = re.compile(self.__NEW_HAND_REGEX)
self.hand_info_re = re.compile(self.__HAND_INFO_REGEX)
self.table_info_re = re.compile(self.__TABLE_INFO_REGEX)
self.player_info_re = re.compile(self.__PLAYER_INFO_REGEX)
self.small_blind_re = re.compile(self.__POST_SB_REGEX)
self.big_blind_re = re.compile(self.__POST_BB_REGEX)
self.both_blinds_re = re.compile(self.__POST_BOTH_REGEX)
self.hand_stage_re = re.compile(self.__HAND_STAGE_REGEX)
self.hole_cards_re = re.compile(self.__HOLE_CARD_REGEX)
self.flop_cards_re = re.compile(self.__FLOP_CARD_REGEX)
self.turn_card_re = re.compile(self.__TURN_CARD_REGEX)
self.river_card_re = re.compile(self.__RIVER_CARD_REGEX)
self.showdown_re = re.compile(self.__SHOWDOWN_REGEX)
self.summary_re = re.compile(self.__SUMMARY_REGEX)
self.uncalled_bet_re = re.compile(self.__UNCALLED_BET_REGEX)
self.collect_pot_re = re.compile(self.__COLLECT_POT_REGEX)
self.hero_cards_re = re.compile(self.__HERO_CARDS_REGEX)
self.cards_shown_re = re.compile(self.__SHOWN_CARDS_REGEX)
self.summary_cards_re = re.compile(self.__SUMMARY_CARDS_REGEX)
self.summary_cards_extra_re = re.compile(self.__SUMMARY_CARDS_EXTRA_REGEX)
self.action_re = re.compile(self.__ACTION_STEP_REGEX)
self.rake_re = re.compile(self.__POT_AND_RAKE_REGEX)
self.showdown_action_re = re.compile(self.__SHOWDOWN_ACTION_REGEX)
# Set methods for plugins to override
def setGameInfoRegex(self, string):
self.__GAME_INFO_REGEX = string
def setSplitHandRegex(self, string):
self.__SPLIT_HAND_REGEX = string
def setNewHandRegex(self, string):
self.__NEW_HAND_REGEX = string
def setHandInfoRegex(self, string):
self.__HAND_INFO_REGEX = string
def setTableInfoRegex(self, string):
self.__TABLE_INFO_REGEX = string
def setPlayerInfoRegex(self, string):
self.__PLAYER_INFO_REGEX = string
def setPostSbRegex(self, string):
self.__POST_SB_REGEX = string
def setPostBbRegex(self, string):
self.__POST_BB_REGEX = string
def setPostBothRegex(self, string):
self.__POST_BOTH_REGEX = string
def setHandStageRegex(self, string):
self.__HAND_STAGE_REGEX = string
def setHoleCardRegex(self, string):
self.__HOLE_CARD_REGEX = string
def setFlopCardRegex(self, string):
self.__FLOP_CARD_REGEX = string
def setTurnCardRegex(self, string):
self.__TURN_CARD_REGEX = string
def setRiverCardRegex(self, string):
self.__RIVER_CARD_REGEX = string
def setShowdownRegex(self, string):
self.__SHOWDOWN_REGEX = string
def setSummaryRegex(self, string):
self.__SUMMARY_REGEX = string
def setUncalledBetRegex(self, string):
self.__UNCALLED_BET_REGEX = string
def setCollectPotRegex(self, string):
self.__COLLECT_POT_REGEX = string
def setHeroCardsRegex(self, string):
self.__HERO_CARDS_REGEX = string
def setShownCardsRegex(self, string):
self.__SHOWN_CARDS_REGEX = string
def setSummaryCardsRegex(self, string):
self.__SUMMARY_CARDS_REGEX = string
def setSummaryCardsExtraRegex(self, string):
self.__SUMMARY_CARDS_EXTRA_REGEX = string
def setActionStepRegex(self, string):
self.__ACTION_STEP_REGEX = string
def setPotAndRakeRegex(self, string):
self.__POT_AND_RAKE_REGEX = string
def setShowdownActionRegex(self, string):
self.__SHOWDOWN_ACTION_REGEX = string

File diff suppressed because it is too large Load Diff

View File

@ -28,167 +28,167 @@ import fpdb_import
class GuiAutoImport (threading.Thread): class GuiAutoImport (threading.Thread):
def __init__(self, settings, config): def __init__(self, settings, config):
"""Constructor for GuiAutoImport""" """Constructor for GuiAutoImport"""
self.settings=settings self.settings=settings
self.config=config self.config=config
imp = self.config.get_import_parameters() imp = self.config.get_import_parameters()
print "Import parameters" print "Import parameters"
print imp print imp
self.input_settings = {} self.input_settings = {}
self.importer = fpdb_import.Importer(self, self.settings, self.config) self.importer = fpdb_import.Importer(self,self.settings, self.config)
self.importer.setCallHud(True) self.importer.setCallHud(True)
self.importer.setMinPrint(30) self.importer.setMinPrint(30)
self.importer.setQuiet(False) self.importer.setQuiet(False)
self.importer.setFailOnError(False) self.importer.setFailOnError(False)
self.importer.setHandCount(0) self.importer.setHandCount(0)
# self.importer.setWatchTime() # self.importer.setWatchTime()
self.server=settings['db-host'] self.server=settings['db-host']
self.user=settings['db-user'] self.user=settings['db-user']
self.password=settings['db-password'] self.password=settings['db-password']
self.database=settings['db-databaseName'] self.database=settings['db-databaseName']
self.mainVBox=gtk.VBox(False,1) self.mainVBox=gtk.VBox(False,1)
self.mainVBox.show() self.mainVBox.show()
self.settingsHBox = gtk.HBox(False, 0) self.settingsHBox = gtk.HBox(False, 0)
self.mainVBox.pack_start(self.settingsHBox, False, True, 0) self.mainVBox.pack_start(self.settingsHBox, False, True, 0)
self.settingsHBox.show() self.settingsHBox.show()
self.intervalLabel = gtk.Label("Time between imports in seconds:") self.intervalLabel = gtk.Label("Interval (ie. break) between imports in seconds:")
self.settingsHBox.pack_start(self.intervalLabel) self.settingsHBox.pack_start(self.intervalLabel)
self.intervalLabel.show() self.intervalLabel.show()
self.intervalEntry=gtk.Entry() self.intervalEntry=gtk.Entry()
self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval"))) self.intervalEntry.set_text(str(self.config.get_import_parameters().get("interval")))
self.settingsHBox.pack_start(self.intervalEntry) self.settingsHBox.pack_start(self.intervalEntry)
self.intervalEntry.show() self.intervalEntry.show()
self.addSites(self.mainVBox) self.addSites(self.mainVBox)
self.startButton=gtk.Button("Start Autoimport") self.startButton=gtk.Button("Start Autoimport")
self.startButton.connect("clicked", self.startClicked, "start clicked") self.startButton.connect("clicked", self.startClicked, "start clicked")
self.mainVBox.add(self.startButton) self.mainVBox.add(self.startButton)
self.startButton.show() self.startButton.show()
#end of GuiAutoImport.__init__ #end of GuiAutoImport.__init__
def browseClicked(self, widget, data): def browseClicked(self, widget, data):
"""runs when user clicks one of the browse buttons in the auto import tab""" """runs when user clicks one of the browse buttons in the auto import tab"""
current_path=data[1].get_text() current_path=data[1].get_text()
dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import", dia_chooser = gtk.FileChooserDialog(title="Please choose the path that you want to auto import",
action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
#dia_chooser.set_current_folder(pathname) #dia_chooser.set_current_folder(pathname)
dia_chooser.set_filename(current_path) dia_chooser.set_filename(current_path)
#dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import #dia_chooser.set_select_multiple(select_multiple) #not in tv, but want this in bulk import
response = dia_chooser.run() response = dia_chooser.run()
if response == gtk.RESPONSE_OK: if response == gtk.RESPONSE_OK:
#print dia_chooser.get_filename(), 'selected' #print dia_chooser.get_filename(), 'selected'
data[1].set_text(dia_chooser.get_filename()) data[1].set_text(dia_chooser.get_filename())
self.input_settings[data[0]][0] = dia_chooser.get_filename() self.input_settings[data[0]][0] = dia_chooser.get_filename()
elif response == gtk.RESPONSE_CANCEL: elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no files selected' print 'Closed, no files selected'
dia_chooser.destroy() dia_chooser.destroy()
#end def GuiAutoImport.browseClicked #end def GuiAutoImport.browseClicked
def do_import(self): def do_import(self):
"""Callback for timer to do an import iteration.""" """Callback for timer to do an import iteration."""
self.importer.runUpdated() self.importer.runUpdated()
print "GuiAutoImport.import_dir done" print "GuiAutoImport.import_dir done"
return True return True
def startClicked(self, widget, data): def startClicked(self, widget, data):
"""runs when user clicks start on auto import tab""" """runs when user clicks start on auto import tab"""
# Check to see if we have an open file handle to the HUD and open one if we do not. # Check to see if we have an open file handle to the HUD and open one if we do not.
# bufsize = 1 means unbuffered # bufsize = 1 means unbuffered
# We need to close this file handle sometime. # We need to close this file handle sometime.
# TODO: Allow for importing from multiple dirs - REB 29AUG2008 # TODO: Allow for importing from multiple dirs - REB 29AUG2008
# As presently written this function does nothing if there is already a pipe open. # As presently written this function does nothing if there is already a pipe open.
# That is not correct. It should open another dir for importing while piping the # That is not correct. It should open another dir for importing while piping the
# results to the same pipe. This means that self.path should be a a list of dirs # results to the same pipe. This means that self.path should be a a list of dirs
# to watch. # to watch.
try: #uhhh, I don't this this is the best way to check for the existence of an attr try: #uhhh, I don't this this is the best way to check for the existence of an attr
getattr(self, "pipe_to_hud") getattr(self, "pipe_to_hud")
except AttributeError: except AttributeError:
if os.name == 'nt': if os.name == 'nt':
command = "python HUD_main.py" + " %s" % (self.database) command = "python HUD_main.py" + " %s" % (self.database)
bs = 0 # windows is not happy with line buffing here bs = 0 # windows is not happy with line buffing here
self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen(command, bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
else: else:
cwd = os.getcwd() cwd = os.getcwd()
command = os.path.join(cwd, 'HUD_main.py') command = os.path.join(cwd, 'HUD_main.py')
bs = 1 bs = 1
self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
universal_newlines=True) universal_newlines=True)
# self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE, # self.pipe_to_hud = subprocess.Popen((command, self.database), bufsize = bs, stdin = subprocess.PIPE,
# universal_newlines=True) # universal_newlines=True)
# command = command + " %s" % (self.database) # command = command + " %s" % (self.database)
# print "command = ", command # print "command = ", command
# self.pipe_to_hud = os.popen(command, 'w') # self.pipe_to_hud = os.popen(command, 'w')
# Add directories to importer object. # Add directories to importer object.
for site in self.input_settings: for site in self.input_settings:
self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1])
print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0]) print "Adding import directories - Site: " + site + " dir: "+ str(self.input_settings[site][0])
self.do_import() self.do_import()
interval=int(self.intervalEntry.get_text()) interval=int(self.intervalEntry.get_text())
gobject.timeout_add(interval*1000, self.do_import) gobject.timeout_add(interval*1000, self.do_import)
#end def GuiAutoImport.startClicked #end def GuiAutoImport.startClicked
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainVBox return self.mainVBox
#end def get_vbox #end def get_vbox
#Create the site line given required info and setup callbacks #Create the site line given required info and setup callbacks
#enabling and disabling sites from this interface not possible #enabling and disabling sites from this interface not possible
#expects a box to layout the line horizontally #expects a box to layout the line horizontally
def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True): def createSiteLine(self, hbox, site, iconpath, hhpath, filter_name, active = True):
label = gtk.Label(site + " auto-import:") label = gtk.Label(site + " auto-import:")
hbox.pack_start(label, False, False, 0) hbox.pack_start(label, False, False, 0)
label.show() label.show()
dirPath=gtk.Entry() dirPath=gtk.Entry()
dirPath.set_text(hhpath) dirPath.set_text(hhpath)
hbox.pack_start(dirPath, False, True, 0) hbox.pack_start(dirPath, False, True, 0)
dirPath.show() dirPath.show()
browseButton=gtk.Button("Browse...") browseButton=gtk.Button("Browse...")
browseButton.connect("clicked", self.browseClicked, [site] + [dirPath]) browseButton.connect("clicked", self.browseClicked, [site] + [dirPath])
hbox.pack_start(browseButton, False, False, 0) hbox.pack_start(browseButton, False, False, 0)
browseButton.show() browseButton.show()
label = gtk.Label(site + " filter:") label = gtk.Label(site + " filter:")
hbox.pack_start(label, False, False, 0) hbox.pack_start(label, False, False, 0)
label.show() label.show()
filter=gtk.Entry() filter=gtk.Entry()
filter.set_text(filter_name) filter.set_text(filter_name)
hbox.pack_start(filter, False, True, 0) hbox.pack_start(filter, False, True, 0)
filter.show() filter.show()
def addSites(self, vbox): def addSites(self, vbox):
for site in self.config.supported_sites.keys(): for site in self.config.supported_sites.keys():
pathHBox = gtk.HBox(False, 0) pathHBox = gtk.HBox(False, 0)
vbox.pack_start(pathHBox, False, True, 0) vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show() pathHBox.show()
paths = self.config.get_default_paths(site) paths = self.config.get_default_paths(site)
params = self.config.get_site_parameters(site) params = self.config.get_site_parameters(site)
self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled']) self.createSiteLine(pathHBox, site, False, paths['hud-defaultPath'], params['converter'], params['enabled'])
self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']] self.input_settings[site] = [paths['hud-defaultPath']] + [params['converter']]
if __name__== "__main__": if __name__== "__main__":
def destroy(*args): # call back for terminating the main eventloop def destroy(*args): # call back for terminating the main eventloop

View File

@ -22,14 +22,17 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os #todo: remove this once import_dir is in fpdb_import import os #todo: remove this once import_dir is in fpdb_import
from time import time
class GuiBulkImport (threading.Thread): class GuiBulkImport (threading.Thread):
def import_dir(self): def import_dir(self):
"""imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it""" """imports a directory, non-recursive. todo: move this to fpdb_import so CLI can use it"""
self.path=self.inputFile self.path=self.inputFile
self.importer.addImportDirectory(self.path) self.importer.addImportDirectory(self.path)
self.importer.setCallHud(False)
starttime = time()
self.importer.runImport() self.importer.runImport()
print "GuiBulkImport.import_dir done" print "GuiBulkImport.import_dir done in %s" %(time() - starttime)
def load_clicked(self, widget, data=None): def load_clicked(self, widget, data=None):
self.inputFile=self.chooser.get_filename() self.inputFile=self.chooser.get_filename()
@ -64,6 +67,7 @@ class GuiBulkImport (threading.Thread):
self.import_dir() self.import_dir()
else: else:
self.importer.addImportFile(self.inputFile) self.importer.addImportFile(self.inputFile)
self.importer.setCallHud(False)
self.importer.runImport() self.importer.runImport()
self.importer.clearFileList() self.importer.clearFileList()
@ -80,7 +84,7 @@ class GuiBulkImport (threading.Thread):
self.db=db self.db=db
self.settings=settings self.settings=settings
self.config=config self.config=config
self.importer = fpdb_import.Importer(self,self.settings) self.importer = fpdb_import.Importer(self,self.settings, config)
self.vbox=gtk.VBox(False,1) self.vbox=gtk.VBox(False,1)
self.vbox.show() self.vbox.show()

View File

@ -20,130 +20,304 @@ import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import os import os
from time import time
#import pokereval #import pokereval
try: try:
from matplotlib.figure import Figure import matplotlib
from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas matplotlib.use('GTK')
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar from matplotlib.figure import Figure
from numpy import arange, cumsum from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from pylab import * from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
from numpy import arange, cumsum
from pylab import *
except: except:
print "Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib if you want to use graphs." print """Failed to load libs for graphing, graphing will not function. Please in
print "This is of no consequence for other parts of the program, e.g. import and HUD are NOT affected by this problem." stall numpy and matplotlib if you want to use graphs."""
print """This is of no consequence for other parts of the program, e.g. import
and HUD are NOT affected by this problem."""
import fpdb_import import fpdb_import
import fpdb_db import fpdb_db
class GuiGraphViewer (threading.Thread): class GuiGraphViewer (threading.Thread):
def get_vbox(self): def get_vbox(self):
"""returns the vbox of this thread""" """returns the vbox of this thread"""
return self.mainVBox return self.mainHBox
#end def get_vbox #end def get_vbox
def showClicked(self, widget, data): def generateGraph(self, widget, data):
try: self.canvas.destroy() try: self.canvas.destroy()
except AttributeError: pass except AttributeError: pass
name=self.nameEntry.get_text() # Whaich sites are selected?
# TODO:
# What hero names for the selected site?
# TODO:
site=self.siteEntry.get_text() name = self.heroes[self.sites]
if site=="PS": if self.sites == "PokerStars":
site=2 site=2
sitename="PokerStars: " sitename="PokerStars: "
elif site=="FTP": elif self.sites=="Full Tilt":
site=1 site=1
sitename="Full Tilt: " sitename="Full Tilt: "
else: else:
print "invalid text in site selection in graph, defaulting to PS" print "invalid text in site selection in graph, defaulting to PS"
site=2 site=2
self.fig = Figure(figsize=(5,4), dpi=100) self.fig = Figure(figsize=(5,4), dpi=100)
#Set graph properties #Set graph properties
self.ax = self.fig.add_subplot(111) self.ax = self.fig.add_subplot(111)
# #Get graph data from DB
self.ax.set_title("Profit graph for ring games") starttime = time()
line = self.getRingProfitGraph(name, site)
print "Graph generated in: %s" %(time() - starttime)
#Set axis labels and grid overlay properites self.ax.set_title("Profit graph for ring games")
self.ax.set_xlabel("Hands", fontsize = 12)
self.ax.set_ylabel("$", fontsize = 12)
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
text = "All Hands, " + sitename + str(name)
self.ax.annotate (text, (61,25), xytext =(0.1, 0.9) , textcoords ="axes fraction" ,) #Set axis labels and grid overlay properites
self.ax.set_xlabel("Hands", fontsize = 12)
self.ax.set_ylabel("$", fontsize = 12)
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
#This line will crash if no hands exist in the query.
text = "All Hands, " + sitename + str(name) + "\nProfit: $" + str(line[-1]) + "\nTotal Hands: " + str(len(line))
#Get graph data from DB self.ax.annotate(text,
line = self.getRingProfitGraph(name, site) xy=(10, -10),
xycoords='axes points',
horizontalalignment='left', verticalalignment='top',
fontsize=10)
#Draw plot
self.ax.plot(line,)
self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea #Draw plot
self.mainVBox.pack_start(self.canvas) self.ax.plot(line,)
self.canvas.show()
#end of def showClicked
def getRingProfitGraph(self, name, site): self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
#self.cursor.execute(self.sql.query['getRingWinningsAllGamesPlayerIdSite'], (name, site)) self.graphBox.add(self.canvas)
self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site)) self.canvas.show()
# returns (HandId,Winnings,Costs,Profit) #end of def showClicked
winnings = self.db.cursor.fetchall()
#profit=range(len(winnings)) def getRingProfitGraph(self, name, site):
#for i in profit: self.cursor.execute(self.sql.query['getRingProfitAllHandsPlayerIdSite'], (name, site))
# self.cursor.execute(self.sql.query['getRingProfitFromHandId'], (name, winnings[i][0], site)) #returns (HandId,Winnings,Costs,Profit)
# spent = self.db.cursor.fetchone() winnings = self.db.cursor.fetchall()
# profit[i]=(i, winnings[i][1]-spent[0])
#y=map(lambda x:float(x[1]), profit) y=map(lambda x:float(x[3]), winnings)
y=map(lambda x:float(x[3]), winnings) line = cumsum(y)
return line/100
line = cumsum(y)
return line/100
#end of def getRingProfitGraph #end of def getRingProfitGraph
def __init__(self, db, settings, querylist, config, debug=True): def createPlayerLine(self, hbox, site, player):
"""Constructor for GraphViewer""" label = gtk.Label(site +" id:")
self.debug=debug hbox.pack_start(label, False, False, 0)
#print "start of GraphViewer constructor" label.show()
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.mainVBox = gtk.VBox(False, 0) pname = gtk.Entry()
self.mainVBox.show() pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
#TODO: Need to connect a callback here
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
self.settingsHBox = gtk.HBox(False, 0) self.__set_hero_name(pname, site)
self.mainVBox.pack_start(self.settingsHBox, False, True, 0)
self.settingsHBox.show()
self.nameLabel = gtk.Label("Name of the player to be graphed:") def __set_hero_name(self, w, site):
self.settingsHBox.pack_start(self.nameLabel) self.heroes[site] = w.get_text()
self.nameLabel.show() print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
self.nameEntry=gtk.Entry() def createSiteLine(self, hbox, site):
self.nameEntry.set_text("name") cb = gtk.CheckButton(site)
self.settingsHBox.pack_start(self.nameEntry) cb.connect('clicked', self.__set_site_select, site)
self.nameEntry.show() hbox.pack_start(cb, False, False, 0)
cb.show()
self.siteLabel = gtk.Label("Site (PS or FTP):") def __set_site_select(self, w, site):
self.settingsHBox.pack_start(self.siteLabel) # This doesn't behave as intended - self.site only allows 1 site for the moment.
self.siteLabel.show() self.sites = site
print "self.sites set to %s" %(self.sites)
self.siteEntry=gtk.Entry() def fillPlayerFrame(self, vbox):
self.siteEntry.set_text("PS") for site in self.conf.supported_sites.keys():
self.settingsHBox.pack_start(self.siteEntry) pathHBox = gtk.HBox(False, 0)
self.siteEntry.show() vbox.pack_start(pathHBox, False, True, 0)
pathHBox.show()
#Note: Assumes PokerStars is in the config player = self.conf.supported_sites[site].screen_name
self.nameEntry.set_text(config.supported_sites["PokerStars"].screen_name) self.createPlayerLine(pathHBox, site, player)
self.showButton=gtk.Button("Show/Refresh") def fillSitesFrame(self, vbox):
self.showButton.connect("clicked", self.showClicked, "show clicked") for site in self.conf.supported_sites.keys():
self.settingsHBox.pack_start(self.showButton) hbox = gtk.HBox(False, 0)
self.showButton.show() vbox.pack_start(hbox, False, True, 0)
#end of GuiGraphViewer.__init__ hbox.show()
self.createSiteLine(hbox, site)
def fillDateFrame(self, vbox):
# Hat tip to Mika Bostrom - calendar code comes from PokerStats
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
hbox.show()
lbl_start = gtk.Label('From:')
lbl_start.show()
btn_start = gtk.Button()
btn_start.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_start.connect('clicked', self.__calendar_dialog, self.start_date)
btn_start.show()
hbox.pack_start(lbl_start, expand=False, padding=3)
hbox.pack_start(btn_start, expand=False, padding=3)
hbox.pack_start(self.start_date, expand=False, padding=2)
self.start_date.show()
#New row for end date
hbox = gtk.HBox()
vbox.pack_start(hbox, False, True, 0)
hbox.show()
lbl_end = gtk.Label(' To:')
lbl_end.show()
btn_end = gtk.Button()
btn_end.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
btn_end.connect('clicked', self.__calendar_dialog, self.end_date)
btn_end.show()
btn_clear = gtk.Button(label=' Clear Dates ')
btn_clear.connect('clicked', self.__clear_dates)
btn_clear.show()
hbox.pack_start(lbl_end, expand=False, padding=3)
hbox.pack_start(btn_end, expand=False, padding=3)
hbox.pack_start(self.end_date, expand=False, padding=2)
self.end_date.show()
hbox.pack_start(btn_clear, expand=False, padding=15)
def __calendar_dialog(self, widget, entry):
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
d.set_title('Pick a date')
vb = gtk.VBox()
cal = gtk.Calendar()
vb.pack_start(cal, expand=False, padding=0)
btn = gtk.Button('Done')
btn.connect('clicked', self.__get_date, cal, entry, d)
vb.pack_start(btn, expand=False, padding=4)
d.add(vb)
d.set_position(gtk.WIN_POS_MOUSE)
d.show_all()
def __clear_dates(self, w):
self.start_date.set_text('')
self.end_date.set_text('')
def __get_dates(self):
t1 = self.start_date.get_text()
t2 = self.end_date.get_text()
return (t1, t2)
def __get_date(self, widget, calendar, entry, win):
# year and day are correct, month is 0..11
(year, month, day) = calendar.get_date()
month += 1
ds = '%04d-%02d-%02d' % (year, month, day)
entry.set_text(ds)
win.destroy()
def exportGraph (self, widget, data):
dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:",
action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
response = dia_chooser.run()
if response == gtk.RESPONSE_OK:
self.exportDir = dia_chooser.get_filename()
elif response == gtk.RESPONSE_CANCEL:
print 'Closed, no graph exported'
dia_chooser.destroy()
def __init__(self, db, settings, querylist, config, debug=True):
"""Constructor for GraphViewer"""
self.debug=debug
#print "start of GraphViewer constructor"
self.db=db
self.cursor=db.cursor
self.settings=settings
self.sql=querylist
self.conf = config
self.sites = "PokerStars"
self.heroes = {}
# For use in date ranges.
self.start_date = gtk.Entry(max=12)
self.end_date = gtk.Entry(max=12)
self.start_date.set_property('editable', False)
self.end_date.set_property('editable', False)
self.mainHBox = gtk.HBox(False, 0)
self.mainHBox.show()
self.leftPanelBox = gtk.VBox(False, 0)
self.graphBox = gtk.VBox(False, 0)
self.hpane = gtk.HPaned()
self.hpane.pack1(self.leftPanelBox)
self.hpane.pack2(self.graphBox)
self.hpane.show()
self.mainHBox.add(self.hpane)
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
sitesFrame = gtk.Frame("Sites:")
sitesFrame.set_label_align(0.0, 0.0)
sitesFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillSitesFrame(vbox)
sitesFrame.add(vbox)
dateFrame = gtk.Frame("Date:")
dateFrame.set_label_align(0.0, 0.0)
dateFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillDateFrame(vbox)
dateFrame.add(vbox)
graphButton=gtk.Button("Generate Graph")
graphButton.connect("clicked", self.generateGraph, "cliced data")
graphButton.show()
self.exportButton=gtk.Button("Export to File")
self.exportButton.connect("clicked", self.exportGraph, "show clicked")
self.exportButton.show()
self.leftPanelBox.add(playerFrame)
self.leftPanelBox.add(sitesFrame)
self.leftPanelBox.add(dateFrame)
self.leftPanelBox.add(graphButton)
self.leftPanelBox.add(self.exportButton)
self.leftPanelBox.show()
self.graphBox.show()

165
pyfpdb/GuiPlayerStats.py Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import threading
import pygtk
pygtk.require('2.0')
import gtk
import os
import fpdb_import
import fpdb_db
import FpdbSQLQueries
class GuiPlayerStats (threading.Thread):
def get_vbox(self):
"""returns the vbox of this thread"""
return self.main_hbox
def toggleCallback(self, widget, data=None):
# print "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()])
self.activesite = data
print "DEBUG: activesite set to %s" %(self.activesite)
def refreshStats(self, widget, data):
try: self.stats_table.destroy()
except AttributeError: pass
self.fillStatsFrame(self.stats_frame)
def fillStatsFrame(self, vbox):
# Get currently active site and grab playerid
tmp = self.sql.query['playerStats']
result = self.cursor.execute(self.sql.query['getPlayerId'], self.heroes[self.activesite])
result = self.db.cursor.fetchall()
pid = result[0][0]
tmp = tmp.replace("<player_test>", "(" + str(pid) + ")")
self.cursor.execute(tmp)
result = self.db.cursor.fetchall()
cols = 18
rows = len(result)+1 # +1 for title row
self.stats_table = gtk.Table(rows, cols, False)
self.stats_table.set_col_spacings(4)
self.stats_table.show()
vbox.add(self.stats_table)
# Create header row
titles = ("GID", "base", "Style", "Site", "$BB", "Hands", "VPIP", "PFR", "saw_f", "sawsd", "wtsdwsf", "wmsd", "FlAFq", "TuAFq", "RvAFq", "PFAFq", "Net($)", "BB/100")
col = 0
row = 0
for t in titles:
l = gtk.Label(titles[col])
l.show()
self.stats_table.attach(l, col, col+1, row, row+1)
col +=1
for row in range(rows-1):
for col in range(cols):
if(row%2 == 0):
bgcolor = "white"
else:
bgcolor = "lightgrey"
eb = gtk.EventBox()
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor))
l = gtk.Label(result[row-1][col])
eb.add(l)
self.stats_table.attach(eb, col, col+1, row+1, row+2)
l.show()
eb.show()
def fillPlayerFrame(self, vbox):
for site in self.conf.supported_sites.keys():
hbox = gtk.HBox(False, 0)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
player = self.conf.supported_sites[site].screen_name
self.createPlayerLine(hbox, site, player)
hbox = gtk.HBox(False, 0)
button = gtk.Button("Refresh")
button.connect("clicked", self.refreshStats, False)
button.show()
hbox.add(button)
vbox.pack_start(hbox, False, True, 0)
hbox.show()
def createPlayerLine(self, hbox, site, player):
if(self.buttongroup == None):
button = gtk.RadioButton(None, site + " id:")
button.set_active(True)
self.buttongroup = button
self.activesite = site
else:
button = gtk.RadioButton(self.buttongroup, site + " id:")
hbox.pack_start(button, True, True, 0)
button.connect("toggled", self.toggleCallback, site)
button.show()
pname = gtk.Entry()
pname.set_text(player)
pname.set_width_chars(20)
hbox.pack_start(pname, False, True, 0)
pname.connect("changed", self.__set_hero_name, site)
#TODO: Look at GtkCompletion - to fill out usernames
pname.show()
self.__set_hero_name(pname, site)
def __set_hero_name(self, w, site):
self.heroes[site] = w.get_text()
print "DEBUG: settings heroes[%s]: %s"%(site, self.heroes[site])
def __init__(self, db, config, querylist, debug=True):
self.debug=debug
self.db=db
self.cursor=db.cursor
self.conf=config
self.sql = querylist
self.activesite = None
self.buttongroup = None
self.heroes = {}
self.stat_table = None
self.stats_frame = None
self.main_hbox = gtk.HBox(False, 0)
self.main_hbox.show()
playerFrame = gtk.Frame("Hero:")
playerFrame.set_label_align(0.0, 0.0)
playerFrame.show()
vbox = gtk.VBox(False, 0)
vbox.show()
self.fillPlayerFrame(vbox)
playerFrame.add(vbox)
statsFrame = gtk.Frame("Stats:")
statsFrame.set_label_align(0.0, 0.0)
statsFrame.show()
self.stats_frame = gtk.VBox(False, 0)
self.stats_frame.show()
self.fillStatsFrame(self.stats_frame)
statsFrame.add(self.stats_frame)
self.main_hbox.pack_start(playerFrame)
self.main_hbox.pack_start(statsFrame)

View File

@ -2,7 +2,7 @@
<FreePokerToolsConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FreePokerToolsConfig.xsd"> <FreePokerToolsConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FreePokerToolsConfig.xsd">
<supported_sites> <supported_sites>
<site enabled="True" site_name="PokerStars" table_finder="PokerStars.exe" screen_name="DO NOT NEED THIS YET" site_path="~/.wine/drive_c/Program Files/PokerStars/" HH_path="~/.wine/drive_c/Program Files/PokerStars/HandHistory/abc/" decoder="pokerstars_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo"> <site enabled="True" site_name="PokerStars" table_finder="PokerStars.exe" screen_name="ENTER HERO NAME" site_path="~/.wine/drive_c/Program Files/PokerStars/" HH_path="~/.wine/drive_c/Program Files/PokerStars/HandHistory/abc/" decoder="pokerstars_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout max="8" width="792" height="546" fav_seat="0"> <layout max="8" width="792" height="546" fav_seat="0">
<location seat="1" x="684" y="61"> </location> <location seat="1" x="684" y="61"> </location>
<location seat="2" x="689" y="239"> </location> <location seat="2" x="689" y="239"> </location>
@ -49,7 +49,7 @@
<location seat="2" x="10" y="288"> </location> <location seat="2" x="10" y="288"> </location>
</layout> </layout>
</site> </site>
<site enabled="True" site_name="Full Tilt" table_finder="FullTiltPoker.exe" screen_name="DO NOT NEED THIS YET" site_path="~/.wine/drive_c/Program Files/Full Tilt Poker/" HH_path="~/.wine/drive_c/Program Files/Full Tilt Poker/HandHistory/abc/" decoder="fulltilt_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo"> <site enabled="True" site_name="Full Tilt" table_finder="FullTiltPoker.exe" screen_name="ENTER HERO NAME" site_path="~/.wine/drive_c/Program Files/Full Tilt Poker/" HH_path="~/.wine/drive_c/Program Files/Full Tilt Poker/HandHistory/abc/" decoder="fulltilt_decode_table" converter="passthrough" supported_games="holdem,razz,omahahi,omahahilo,studhi,studhilo">
<layout fav_seat="0" height="547" max="8" width="794"> <layout fav_seat="0" height="547" max="8" width="794">
<location seat="1" x="640" y="64"> </location> <location seat="1" x="640" y="64"> </location>
<location seat="2" x="650" y="230"> </location> <location seat="2" x="650" y="230"> </location>
@ -84,7 +84,7 @@
<location seat="9" x="70" y="53"> </location> <location seat="9" x="70" y="53"> </location>
</layout> </layout>
</site> </site>
<site enabled="False" site_name="Everleaf" table_finder="Poker.exe" screen_name="DO NOT NEED THIS YET" site_path="" HH_path="" decoder="Unknown" converter="EverleafToFpdb" supported_games="holdem,razz,omahahi,omahahilo,studhi" fgcolor="#48D1CC" bgcolor="#000000"> <site enabled="False" site_name="Everleaf" table_finder="Poker.exe" screen_name="ENTER HERO NAME" site_path="" HH_path="" decoder="Unknown" converter="EverleafToFpdb" supported_games="holdem,razz,omahahi,omahahilo,studhi" fgcolor="#48D1CC" bgcolor="#000000" opacity="0.75">
<layout fav_seat="0" height="546" max="6" width="792"> <layout fav_seat="0" height="546" max="6" width="792">
<location seat="1" x="581" y="109"> </location> <location seat="1" x="581" y="109"> </location>
<location seat="2" x="605" y="287"> </location> <location seat="2" x="605" y="287"> </location>
@ -187,7 +187,7 @@
<pu_stat pu_stat_name="ffreq_4"> </pu_stat> <pu_stat pu_stat_name="ffreq_4"> </pu_stat>
</pu> </pu>
</popup_windows> </popup_windows>
<import callFpdbHud = "True" interval = "10" ></import> <import callFpdbHud = "True" interval = "10" hhArchiveBase="~/.fpdb/HandHistories/"></import>
<tv combinedStealFold = "True" combined2B3B = "True" combinedPostflop = "True"></tv> <tv combinedStealFold = "True" combined2B3B = "True" combinedPostflop = "True"></tv>
<supported_databases> <supported_databases>

View File

@ -0,0 +1,427 @@
#!/usr/bin/python
#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 Configuration
import FpdbRegex
import re
import sys
import traceback
import os
import os.path
import xml.dom.minidom
from decimal import Decimal
import operator
from xml.dom.minidom import Node
class HandHistoryConverter:
def __init__(self, config, file, sitename):
print "HandHistory init called"
self.c = config
self.sitename = sitename
self.obs = "" # One big string
self.filetype = "text"
self.doc = None # For XML based HH files
self.file = file
self.hhbase = self.c.get_import_parameters().get("hhArchiveBase")
self.hhbase = os.path.expanduser(self.hhbase)
self.hhdir = os.path.join(self.hhbase,sitename)
self.gametype = []
# self.ofile = os.path.join(self.hhdir,file)
self.rexx = FpdbRegex.FpdbRegex()
def __str__(self):
tmp = "HandHistoryConverter: '%s'\n" % (self.sitename)
tmp = tmp + "\thhbase: '%s'\n" % (self.hhbase)
tmp = tmp + "\thhdir: '%s'\n" % (self.hhdir)
tmp = tmp + "\tfiletype: '%s'\n" % (self.filetype)
tmp = tmp + "\tinfile: '%s'\n" % (self.file)
# tmp = tmp + "\toutfile: '%s'\n" % (self.ofile)
# tmp = tmp + "\tgametype: '%s'\n" % (self.gametype[0])
# tmp = tmp + "\tgamebase: '%s'\n" % (self.gametype[1])
# tmp = tmp + "\tlimit: '%s'\n" % (self.gametype[2])
# tmp = tmp + "\tsb/bb: '%s/%s'\n" % (self.gametype[3], self.gametype[4])
return tmp
def processFile(self):
if not self.sanityCheck():
print "Cowardly refusing to continue after failed sanity check"
return
self.readFile(self.file)
self.gametype = self.determineGameType()
self.hands = self.splitFileIntoHands()
for hand in self.hands:
self.readHandInfo(hand)
self.readPlayerStacks(hand)
self.markStreets(hand)
self.readBlinds(hand)
self.readHeroCards(hand)
# Read action (Note: no guarantee this is in hand order.
for street in hand.streets.groupdict():
self.readAction(hand, street)
# finalise it (total the pot)
hand.totalPot()
self.getRake(hand)
if(hand.involved == True):
#self.writeHand("output file", hand)
hand.printHand()
else:
pass #Don't write out observed hands
#####
# These functions are parse actions that may be overridden by the inheriting class
#
def readSupportedGames(self): abstract
# should return a list
# type base limit
# [ ring, hold, nl , sb, bb ]
# Valid types specified in docs/tabledesign.html in Gametypes
def determineGameType(self): abstract
# Read any of:
# HID HandID
# TABLE Table name
# SB small blind
# BB big blind
# GAMETYPE gametype
# YEAR MON DAY HR MIN SEC datetime
# BUTTON button seat number
def readHandInfo(self, hand): abstract
# Needs to return a list of lists in the format
# [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]]
def readPlayerStacks(self, hand): abstract
# Needs to return a MatchObject with group names identifying the streets into the Hand object
# that is, pulls the chunks of preflop, flop, turn and river text into hand.streets MatchObject.
def markStreets(self, hand): abstract
#Needs to return a list in the format
# ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb,
# addtional players are assumed to post a bb oop
def readBlinds(self, hand): abstract
def readHeroCards(self, hand): abstract
def readAction(self, hand, street): abstract
# Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated
# so that an inheriting class can calculate it for the specific site if need be.
def getRake(self, hand): abstract
def sanityCheck(self):
sane = True
base_w = False
#Check if hhbase exists and is writable
#Note: Will not try to create the base HH directory
if not (os.access(self.hhbase, os.W_OK) and os.path.isdir(self.hhbase)):
print "HH Sanity Check: Directory hhbase '" + self.hhbase + "' doesn't exist or is not writable"
else:
#Check if hhdir exists and is writable
if not os.path.isdir(self.hhdir):
# In first pass, dir may not exist. Attempt to create dir
print "Creating directory: '%s'" % (self.hhdir)
os.mkdir(self.hhdir)
sane = True
elif os.access(self.hhdir, os.W_OK):
sane = True
else:
print "HH Sanity Check: Directory hhdir '" + self.hhdir + "' or its parent directory are not writable"
return sane
# Functions not necessary to implement in sub class
def setFileType(self, filetype = "text"):
self.filetype = filetype
def splitFileIntoHands(self):
hands = []
list = self.rexx.split_hand_re.split(self.obs)
list.pop() #Last entry is empty
for l in list:
# print "'" + l + "'"
hands = hands + [Hand(self.sitename, self.gametype, l)]
return hands
def readFile(self, filename):
"""Read file"""
print "Reading file: '%s'" %(filename)
if(self.filetype == "text"):
infile=open(filename, "rU")
self.obs = infile.read()
infile.close()
elif(self.filetype == "xml"):
try:
doc = xml.dom.minidom.parse(filename)
self.doc = doc
except:
traceback.print_exc(file=sys.stderr)
def writeHand(self, file, hand):
"""Write out parsed data"""
print "DEBUG: *************************"
print "DEBUG: Start of print hand"
print "DEBUG: *************************"
print "%s Game #%s: %s ($%s/$%s) - %s" %(hand.sitename, hand.handid, "XXXXhand.gametype", hand.sb, hand.bb, hand.starttime)
print "Table '%s' %d-max Seat #%s is the button" %(hand.tablename, hand.maxseats, hand.buttonpos)
for player in hand.players:
print "Seat %s: %s ($%s)" %(player[0], player[1], player[2])
if(hand.posted[0] == "FpdbNBP"):
print "No small blind posted"
else:
print "%s: posts small blind $%s" %(hand.posted[0], hand.sb)
#May be more than 1 bb posting
print "%s: posts big blind $%s" %(hand.posted[1], hand.bb)
if(len(hand.posted) > 2):
# Need to loop on all remaining big blinds - lazy
print "XXXXXXXXX FIXME XXXXXXXX"
print "*** HOLE CARDS ***"
print "Dealt to %s [%s %s]" %(hand.hero , hand.holecards[0], hand.holecards[1])
#
## ACTION STUFF
# This is no limit only at the moment
for act in hand.actions['PREFLOP']:
self.printActionLine(act, 0)
if 'PREFLOP' in hand.actions:
for act in hand.actions['PREFLOP']:
print "PF action"
if 'FLOP' in hand.actions:
print "*** FLOP *** [%s %s %s]" %(hand.streets.group("FLOP1"), hand.streets.group("FLOP2"), hand.streets.group("FLOP3"))
for act in hand.actions['FLOP']:
self.printActionLine(act, 0)
if 'TURN' in hand.actions:
print "*** TURN *** [%s %s %s] [%s]" %(hand.streets.group("FLOP1"), hand.streets.group("FLOP2"), hand.streets.group("FLOP3"), hand.streets.group("TURN1"))
for act in hand.actions['TURN']:
self.printActionLine(act, 0)
if 'RIVER' in hand.actions:
print "*** RIVER *** [%s %s %s %s] [%s]" %(hand.streets.group("FLOP1"), hand.streets.group("FLOP2"), hand.streets.group("FLOP3"), hand.streets.group("TURN1"), hand.streets.group("RIVER1"))
for act in hand.actions['RIVER']:
self.printActionLine(act, 0)
print "*** SUMMARY ***"
print "XXXXXXXXXXXX Need sumary info XXXXXXXXXXX"
# print "Total pot $%s | Rake $%s)" %(hand.totalpot $" + hand.rake)
# print "Board [" + boardcards + "]"
#
# SUMMARY STUFF
def printActionLine(self, act, pot):
if act[1] == 'folds' or act[1] == 'checks':
print "%s: %s " %(act[0], act[1])
if act[1] == 'calls':
print "%s: %s $%s" %(act[0], act[1], act[2])
if act[1] == 'raises':
print "%s: %s $%s to XXXpottotalXXX" %(act[0], act[1], act[2])
#takes a poker float (including , for thousand seperator and converts it to an int
def float2int (self, string):
pos=string.find(",")
if (pos!=-1): #remove , the thousand seperator
string=string[0:pos]+string[pos+1:]
pos=string.find(".")
if (pos!=-1): #remove decimal point
string=string[0:pos]+string[pos+1:]
result = int(string)
if pos==-1: #no decimal point - was in full dollars - need to multiply with 100
result*=100
return result
#end def float2int
class Hand:
# def __init__(self, sitename, gametype, sb, bb, string):
UPS = {'a':'A', 't':'T', 'j':'J', 'q':'Q', 'k':'K'}
STREETS = ['BLINDS','PREFLOP','FLOP','TURN','RIVER']
def __init__(self, sitename, gametype, string):
self.sitename = sitename
self.gametype = gametype
self.string = string
self.streets = None # A MatchObject using a groupnames to identify streets.
self.actions = {}
self.handid = 0
self.sb = gametype[3]
self.bb = gametype[4]
self.tablename = "Slartibartfast"
self.maxseats = 10
self.counted_seats = 0
self.buttonpos = 0
self.seating = []
self.players = []
self.posted = []
self.involved = True
self.hero = "Hiro"
self.holecards = "Xx Xx"
self.action = []
self.totalpot = None
self.rake = None
self.bets = {}
self.lastBet = {}
for street in self.STREETS:
self.bets[street] = {}
self.lastBet[street] = 0
def addPlayer(self, seat, name, chips):
"""seat, an int indicating the seat
name, the player name
chips, the chips the player has at the start of the hand"""
#self.players.append(name)
self.players.append([seat, name, chips])
#self.startChips[name] = chips
#self.endChips[name] = chips
#self.winners[name] = 0
for street in self.STREETS:
self.bets[street][name] = []
def addHoleCards(self,h1,h2,seat=None): # generalise to add hole cards for a specific seat or player
self.holecards = [self.card(h1), self.card(h2)]
def card(self,c):
"""upper case the ranks but not suits, 'atjqk' => 'ATJQK'"""
# don't know how to make this 'static'
for k,v in self.UPS.items():
c = c.replace(k,v)
return c
def addBlind(self, player, amount):
# if player is None, it's a missing small blind.
if player is not None:
self.bets['PREFLOP'][player].append(Decimal(amount))
self.lastBet['PREFLOP'] = Decimal(amount)
self.posted += [player]
def addCall(self, street, player=None, amount=None):
# Potentially calculate the amount of the call if not supplied
# corner cases include if player would be all in
if amount is not None:
self.bets[street][player].append(Decimal(amount))
#self.lastBet[street] = Decimal(amount)
self.actions[street] += [[player, 'calls', amount]]
def addRaiseTo(self, street, player, amountTo):
# Given only the amount raised to, the amount of the raise can be calculated by
# working out how much this player has already in the pot
# (which is the sum of self.bets[street][player])
# and how much he needs to call to match the previous player
# (which is tracked by self.lastBet)
committedThisStreet = reduce(operator.add, self.bets[street][player], 0)
amountToCall = self.lastBet[street] - committedThisStreet
self.lastBet[street] = Decimal(amountTo)
amountBy = Decimal(amountTo) - amountToCall
self.bets[street][player].append(amountBy+amountToCall)
self.actions[street] += [[player, 'raises', amountBy, amountTo]]
def addBet(self, street, player=None, amount=0):
self.bets[street][name].append(Decimal(amount))
self.orderedBets[street].append(Decimal(amount))
self.actions[street] += [[player, 'bets', amount]]
def totalPot(self):
if self.totalpot is None:
self.totalpot = 0
# player names:
# print [x[1] for x in self.players]
for player in [x[1] for x in self.players]:
for street in self.STREETS:
print street, self.bets[street][player]
self.totalpot += reduce(operator.add, self.bets[street][player], 0)
def printHand(self):
# PokerStars format.
print "### DEBUG ###"
print "%s Game #%s: %s ($%s/$%s) - %s" %(self.sitename, self.handid, "XXXXhand.gametype", self.sb, self.bb, self.starttime)
print "Table '%s' %d-max Seat #%s is the button" %(self.tablename, self.maxseats, self.buttonpos)
for player in self.players:
print "Seat %s: %s ($%s)" %(player[0], player[1], player[2])
if(self.posted[0] is None):
print "No small blind posted"
else:
print "%s: posts small blind $%s" %(self.posted[0], self.sb)
#May be more than 1 bb posting
for a in self.posted[1:]:
print "%s: posts big blind $%s" %(self.posted[1], self.bb)
# What about big & small blinds?
print "*** HOLE CARDS ***"
print "Dealt to %s [%s %s]" %(self.hero , self.holecards[0], self.holecards[1])
if 'PREFLOP' in self.actions:
for act in self.actions['PREFLOP']:
self.printActionLine(act)
if 'FLOP' in self.actions:
print "*** FLOP *** [%s %s %s]" %(self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"))
for act in self.actions['FLOP']:
self.printActionLine(act)
if 'TURN' in self.actions:
print "*** TURN *** [%s %s %s] [%s]" %(self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"), self.streets.group("TURN1"))
for act in self.actions['TURN']:
self.printActionLine(act)
if 'RIVER' in self.actions:
print "*** RIVER *** [%s %s %s %s] [%s]" %(self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"), self.streets.group("TURN1"), self.streets.group("RIVER1"))
for act in self.actions['RIVER']:
self.printActionLine(act)
#Some sites don't have a showdown section so we have to figure out if there should be one
# The logic for a showdown is: at the end of river action there are at least two players in the hand
if 'SHOWDOWN' in self.actions:
print "*** SHOW DOWN ***"
print "what do they show"
print "*** SUMMARY ***"
print "Total pot $%s | Rake $%s)" % (self.totalpot, self.rake)
print "Board [%s %s %s %s %s]" % (self.streets.group("FLOP1"), self.streets.group("FLOP2"), self.streets.group("FLOP3"), self.streets.group("TURN1"), self.streets.group("RIVER1"))
def printActionLine(self, act):
if act[1] == 'folds' or act[1] == 'checks':
print "%s: %s " %(act[0], act[1])
if act[1] == 'calls':
print "%s: %s $%s" %(act[0], act[1], act[2])
if act[1] == 'raises':
print "%s: %s $%s to $%s" %(act[0], act[1], act[2], act[3])

View File

@ -60,9 +60,9 @@ class Hud:
self.stat_windows = {} self.stat_windows = {}
self.popup_windows = {} self.popup_windows = {}
self.aux_windows = [] self.aux_windows = []
self.font = pango.FontDescription("Sans 8") self.font = pango.FontDescription("Sans 7")
# Set up a main window for this this instance of the HUD # Set up a main window for this this instance of the HUD
self.main_window = gtk.Window() self.main_window = gtk.Window()
# self.window.set_decorated(0) # self.window.set_decorated(0)
self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC) self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC)
@ -92,14 +92,22 @@ class Hud:
self.menu.append(self.item1) self.menu.append(self.item1)
self.item1.connect("activate", self.kill_hud) self.item1.connect("activate", self.kill_hud)
self.item1.show() self.item1.show()
self.item2 = gtk.MenuItem('Save Layout') self.item2 = gtk.MenuItem('Save Layout')
self.menu.append(self.item2) self.menu.append(self.item2)
self.item2.connect("activate", self.save_layout) self.item2.connect("activate", self.save_layout)
self.item2.show() self.item2.show()
self.item3 = gtk.MenuItem('Reposition Stats') self.item3 = gtk.MenuItem('Reposition Stats')
self.menu.append(self.item3) self.menu.append(self.item3)
self.item3.connect("activate", self.reposition_windows) self.item3.connect("activate", self.reposition_windows)
self.item3.show() self.item3.show()
self.item4 = gtk.MenuItem('Debug Stat Windows')
self.menu.append(self.item4)
self.item4.connect("activate", self.debug_stat_windows)
self.item4.show()
self.ebox.connect_object("button-press-event", self.on_button_press, self.menu) self.ebox.connect_object("button-press-event", self.on_button_press, self.menu)
self.main_window.show_all() self.main_window.show_all()
@ -132,6 +140,12 @@ class Hud:
for w in self.stat_windows: for w in self.stat_windows:
self.stat_windows[w].window.move(self.stat_windows[w].x, self.stat_windows[w].window.move(self.stat_windows[w].x,
self.stat_windows[w].y) self.stat_windows[w].y)
def debug_stat_windows(self, *args):
print self.table, "\n", self.main_window.window.get_transient_for()
for w in self.stat_windows:
print self.stat_windows[w].window.window.get_transient_for()
def save_layout(self, *args): def save_layout(self, *args):
new_layout = [(0, 0)] * self.max new_layout = [(0, 0)] * self.max
# todo: have the hud track the poker table's window position regularly, don't forget to update table.x and table.y. # todo: have the hud track the poker table's window position regularly, don't forget to update table.x and table.y.
@ -215,7 +229,14 @@ class Hud:
this_stat = config.supported_games[self.poker_game].stats[self.stats[r][c]] this_stat = config.supported_games[self.poker_game].stats[self.stats[r][c]]
number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c]) number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c])
statstring = this_stat.hudprefix + str(number[1]) + this_stat.hudsuffix statstring = this_stat.hudprefix + str(number[1]) + this_stat.hudsuffix
if this_stat.hudcolor != "":
self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor']))
self.stat_windows[stat_dict[s]['seat']].label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.hudcolor))
self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(statstring) self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(statstring)
if statstring != "xxx":
self.stat_windows[stat_dict[s]['seat']].window.show_all()
tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \ tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \
number[3] + ", " + number[4] number[3] + ", " + number[4]
Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip) Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip)
@ -281,6 +302,7 @@ class Stat_Window:
self.double_click(widget, event, *args) self.double_click(widget, event, *args)
if event.button == 2: # middle button event if event.button == 2: # middle button event
self.window.hide()
# print "middle button clicked" # print "middle button clicked"
pass pass
@ -361,42 +383,14 @@ class Stat_Window:
self.e_box[r][c].add(self.label[r][c]) self.e_box[r][c].add(self.label[r][c])
self.e_box[r][c].connect("button_press_event", self.button_press_cb) self.e_box[r][c].connect("button_press_event", self.button_press_cb)
# font = pango.FontDescription("Sans 8") font = pango.FontDescription("Sans 7")
self.label[r][c].modify_font(font) self.label[r][c].modify_font(font)
# if not os.name == 'nt': # seems to be a bug in opacity on windows
self.window.set_opacity(parent.colors['hudopacity']) self.window.set_opacity(parent.colors['hudopacity'])
self.window.realize
self.window.move(self.x, self.y) self.window.move(self.x, self.y)
self.window.show_all()
# set_keep_above(1) for windows
if os.name == 'nt': self.topify_window(self.window)
def topify_window(self, window): self.window.hide()
"""Set the specified gtk window to stayontop in MS Windows."""
def windowEnumerationHandler(hwnd, resultList):
'''Callback for win32gui.EnumWindows() to generate list of window handles.'''
resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
unique_name = 'unique name for finding this window'
real_name = window.get_title()
window.set_title(unique_name)
tl_windows = []
win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
for w in tl_windows:
if w[1] == unique_name:
#win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE)
# style = win32gui.GetWindowLong(w[0], win32con.GWL_EXSTYLE)
# style |= win32con.WS_EX_TOOLWINDOW
# style &= ~win32con.WS_EX_APPWINDOW
# win32gui.SetWindowLong(w[0], win32con.GWL_EXSTYLE, style)
win32gui.ShowWindow(w[0], win32con.SW_SHOW)
window.set_title(real_name)
def destroy(*args): # call back for terminating the main eventloop def destroy(*args): # call back for terminating the main eventloop
gtk.main_quit() gtk.main_quit()
@ -431,7 +425,7 @@ class Popup_window:
self.lab.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudbgcolor'])) self.lab.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudbgcolor']))
self.lab.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudfgcolor'])) self.lab.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(stat_window.parent.colors['hudfgcolor']))
self.window.realize # self.window.realize()
# figure out the row, col address of the click that activated the popup # figure out the row, col address of the click that activated the popup
row = 0 row = 0
@ -564,7 +558,7 @@ if __name__== "__main__":
c = Configuration.Config() c = Configuration.Config()
#tables = Tables.discover(c) #tables = Tables.discover(c)
t = Tables.discover_table_by_name(c, "Chelsea") t = Tables.discover_table_by_name(c, "Motorway")
if t is None: if t is None:
print "Table not found." print "Table not found."
db = Database.Database(c, 'fpdb', 'holdem') db = Database.Database(c, 'fpdb', 'holdem')

View File

@ -41,30 +41,47 @@ import Configuration
import Database import Database
import Tables import Tables
import Hud import Hud
import Mucked
import HandHistory import HandHistory
class Mucked: class Aux_Window:
def __init__(self, parent, db_connection): def __init__(self, parent, config, db_name):
self.config = config
self.parent = parent #this is the parent of the mucked cards widget self.parent = parent #this is the parent of the mucked cards widget
self.db_connection = db_connection self.db_name = db_name
self.vbox = gtk.VBox() self.vbox = gtk.VBox()
self.parent.add(self.vbox) self.parent.add(self.vbox)
self.mucked_list = MuckedList (self.vbox, db_connection) def update(self):
self.mucked_cards = MuckedCards(self.vbox, db_connection) pass
class Stud_mucked(Aux_Window):
def __init__(self, parent, config, db_name):
self.config = config
self.parent = parent #this is the parent of the mucked cards widget
self.db_name = db_name
self.vbox = gtk.VBox()
self.parent.add(self.vbox)
self.mucked_list = Stud_list(self.vbox, config, db_name)
self.mucked_cards = Stud_cards(self.vbox, config, db_name)
self.mucked_list.mucked_cards = self.mucked_cards self.mucked_list.mucked_cards = self.mucked_cards
self.parent.show_all()
def update(self, new_hand_id): def update_data(self, new_hand_id):
self.mucked_list.update(new_hand_id) self.mucked_list.update_data(new_hand_id)
class MuckedList: def update_gui(self, new_hand_id):
def __init__(self, parent, db_connection): self.mucked_list.update_gui(new_hand_id)
self.parent = parent class Stud_list:
self.db_connection = db_connection def __init__(self, parent, config, db_name):
self.parent = parent
self.config = config
self.db_name = db_name
# set up a scrolled window to hold the listbox # set up a scrolled window to hold the listbox
self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window = gtk.ScrolledWindow()
@ -115,11 +132,25 @@ class MuckedList:
vadj.set_value(vadj.upper) vadj.set_value(vadj.upper)
self.mucked_cards.update(new_hand_id) self.mucked_cards.update(new_hand_id)
class MuckedCards: def update_data(self, new_hand_id):
def __init__(self, parent, db_connection): self.info_row = ((new_hand_id, "xxxx", 0), )
self.mucked_cards.update_data(new_hand_id)
self.parent = parent #this is the parent of the mucked cards widget def update_gui(self, new_hand_id):
self.db_connection = db_connection iter = self.liststore.append(self.info_row[0])
sel = self.treeview.get_selection()
sel.select_iter(iter)
vadj = self.scrolled_window.get_vadjustment()
vadj.set_value(vadj.upper)
self.mucked_cards.update_gui(new_hand_id)
class Stud_cards:
def __init__(self, parent, config, db_name = 'fpdb'):
self.parent = parent #this is the parent of the mucked cards widget
self.config = config
self.db_name = db_name
self.card_images = self.get_card_images() self.card_images = self.get_card_images()
self.seen_cards = {} self.seen_cards = {}
@ -159,44 +190,106 @@ class MuckedCards:
self.parent.add(self.grid) self.parent.add(self.grid)
def translate_cards(self, old_cards): def translate_cards(self, old_cards):
pass ranks = ('', '', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A')
for c in old_cards.keys():
for i in range(1, 8):
rank = 'card' + str(i) + 'Value'
suit = 'card' + str(i) + 'Suit'
key = 'hole_card_' + str(i)
if old_cards[c][rank] == 0:
old_cards[c][key] = 'xx'
else:
old_cards[c][key] = ranks[old_cards[c][rank]] + old_cards[c][suit]
return old_cards
def update(self, new_hand_id): def update(self, new_hand_id):
cards = self.db_connection.get_cards(new_hand_id) db_connection = Database.Database(self.config, 'fpdb', '')
cards = db_connection.get_cards(new_hand_id)
self.clear() self.clear()
cards = self.translate_cards(cards) cards = self.translate_cards(cards)
for c in cards.keys(): for c in cards.keys():
self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name']) self.grid_contents[(1, cards[c]['seat_number'] - 1)].set_text(cards[c]['screen_name'])
for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'), for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'),
(4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')): (4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')):
if not cards[c][i[1]] == "": if not cards[c][i[1]] == "xx":
self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \ self.seen_cards[(i[0], cards[c]['seat_number'] - 1)]. \
set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])]) set_from_pixbuf(self.card_images[self.split_cards(cards[c][i[1]])])
xml_text = self.db_connection.get_xml(new_hand_id) tips = []
hh = HandHistory.HandHistory(xml_text, ('BETTING')) action = db_connection.get_action_from_hand(new_hand_id)
for street in action:
temp = ''
for act in street:
temp = temp + act[0] + " " + act[1] + "s "
if act[2] > 0:
if act[2]%100 > 0:
temp = temp + "%4.2f\n" % (float(act[2])/100)
else:
temp = temp + "%d\n" % (act[2]/100)
else:
temp = temp + "\n"
tips.append(temp)
# action in tool tips for 3rd street cards ## action in tool tips for 3rd street cards
tip = "%s" % hh.BETTING.rounds[0]
for c in (0, 1, 2): for c in (0, 1, 2):
for r in range(0, self.rows): for r in range(0, self.rows):
self.eb[(c, r)].set_tooltip_text(tip) self.eb[(c, r)].set_tooltip_text(tips[0])
# action in tools tips for later streets # action in tools tips for later streets
round_to_col = (0, 3, 4, 5, 6) round_to_col = (0, 3, 4, 5, 6)
for round in range(1, len(hh.BETTING.rounds)): for round in range(1, len(tips)):
tip = "%s" % hh.BETTING.rounds[round]
for r in range(0, self.rows): for r in range(0, self.rows):
self.eb[(round_to_col[round], r)].set_tooltip_text(tip) self.eb[(round_to_col[round], r)].set_tooltip_text(tips[round])
db_connection.close_connection()
def update_data(self, new_hand_id):
db_connection = Database.Database(self.config, 'fpdb', '')
cards = db_connection.get_cards(new_hand_id)
self.clear()
self.cards = self.translate_cards(cards)
self.tips = []
action = db_connection.get_action_from_hand(new_hand_id)
for street in action:
temp = ''
for act in street:
temp = temp + act[0] + " " + act[1] + "s "
if act[2] > 0:
if act[2]%100 > 0:
temp = temp + "%4.2f\n" % (float(act[2])/100)
else:
temp = temp + "%d\n" % (act[2]/100)
else:
temp = temp + "\n"
self.tips.append(temp)
db_connection.close_connection()
def update_gui(self, new_hand_id):
for c in self.cards.keys():
self.grid_contents[(1, self.cards[c]['seat_number'] - 1)].set_text(self.cards[c]['screen_name'])
for i in ((0, 'hole_card_1'), (1, 'hole_card_2'), (2, 'hole_card_3'), (3, 'hole_card_4'),
(4, 'hole_card_5'), (5, 'hole_card_6'), (6, 'hole_card_7')):
if not self.cards[c][i[1]] == "xx":
self.seen_cards[(i[0], self.cards[c]['seat_number'] - 1)]. \
set_from_pixbuf(self.card_images[self.split_cards(self.cards[c][i[1]])])
## action in tool tips for 3rd street cards
for c in (0, 1, 2):
for r in range(0, self.rows):
self.eb[(c, r)].set_tooltip_text(self.tips[0])
# action in tools tips for later streets
round_to_col = (0, 3, 4, 5, 6)
for round in range(1, len(self.tips)):
for r in range(0, self.rows):
self.eb[(round_to_col[round], r)].set_tooltip_text(self.tips[round])
def split_cards(self, card): def split_cards(self, card):
return (card[0], card[1].upper()) return (card[0], card[1].upper())
def clear(self): def clear(self):
for r in range(0, self.rows): for r in range(0, self.rows):
self.grid_contents[(1, r)].set_text(" ") self.grid_contents[(1, r)].set_text(" ")
for c in range(0, 7): for c in range(0, 7):
self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')]) self.seen_cards[(c, r)].set_from_pixbuf(self.card_images[('B', 'S')])
self.eb[(c, r)].set_tooltip_text('') self.eb[(c, r)].set_tooltip_text('')
@ -225,19 +318,19 @@ if __name__== "__main__":
# just read it and pass it to update # just read it and pass it to update
new_hand_id = sys.stdin.readline() new_hand_id = sys.stdin.readline()
new_hand_id = new_hand_id.rstrip() # remove trailing whitespace new_hand_id = new_hand_id.rstrip() # remove trailing whitespace
m.update(new_hand_id) m.update_data(new_hand_id)
m.update_gui(new_hand_id)
return(True) return(True)
config = Configuration.Config() config = Configuration.Config()
db_connection = Database.Database(config, 'fpdb', '') # db_connection = Database.Database(config, 'fpdb', '')
main_window = gtk.Window() main_window = gtk.Window()
main_window.set_keep_above(True) main_window.set_keep_above(True)
main_window.connect("destroy", destroy) main_window.connect("destroy", destroy)
m = Mucked(main_window, db_connection) aux_to_call = "Stud_mucked"
m = eval("%s(main_window, config, 'fpdb')" % aux_to_call)
main_window.show_all() main_window.show_all()
s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand) s_id = gobject.io_add_watch(sys.stdin, gobject.IO_IN, process_new_hand)
gtk.main() gtk.main()

View File

@ -24,63 +24,83 @@
import os import os
import sys import sys
import datetime
import Configuration
import fpdb_db import fpdb_db
import fpdb_import import fpdb_import
import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
import unittest import unittest
class TestSequenceFunctions(unittest.TestCase): class TestSequenceFunctions(unittest.TestCase):
def setUp(self): def setUp(self):
"""Configure MySQL settings/database and establish connection""" """Configure MySQL settings/database and establish connection"""
self.mysql_settings={ 'db-host':"localhost", self.c = Configuration.Config()
'db-backend':2, self.mysql_settings={ 'db-host':"localhost",
'db-databaseName':"fpdbtest", 'db-backend':2,
'db-user':"fpdb", 'db-databaseName':"fpdbtest",
'db-password':"fpdb"} 'db-user':"fpdb",
self.mysql_db = fpdb_db.fpdb_db() 'db-password':"fpdb"}
self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'], self.mysql_db = fpdb_db.fpdb_db()
self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'], self.mysql_db.connect(self.mysql_settings['db-backend'], self.mysql_settings['db-host'],
self.mysql_settings['db-password']) self.mysql_settings['db-databaseName'], self.mysql_settings['db-user'],
self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB') self.mysql_settings['db-password'])
self.mysqlimporter = fpdb_import.Importer(self, self.mysql_settings) self.mysqldict = FpdbSQLQueries.FpdbSQLQueries('MySQL InnoDB')
self.mysqlimporter = fpdb_import.Importer(self, self.mysql_settings, self.c)
self.mysqlimporter.setCallHud(False)
# """Configure Postgres settings/database and establish connection""" # """Configure Postgres settings/database and establish connection"""
# self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"} # self.pg_settings={ 'db-host':"localhost", 'db-backend':3, 'db-databaseName':"fpdbtest", 'db-user':"fpdb", 'db-password':"fpdb"}
# self.pg_db = fpdb_db.fpdb_db() # self.pg_db = fpdb_db.fpdb_db()
# self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'], # self.pg_db.connect(self.pg_settings['db-backend'], self.pg_settings['db-host'],
# self.pg_settings['db-databaseName'], self.pg_settings['db-user'], # self.pg_settings['db-databaseName'], self.pg_settings['db-user'],
# self.pg_settings['db-password']) # self.pg_settings['db-password'])
# self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL') # self.pgdict = FpdbSQLQueries.FpdbSQLQueries('PostgreSQL')
def testDatabaseConnection(self): def testDatabaseConnection(self):
"""Test all supported DBs""" """Test all supported DBs"""
self.result = self.mysql_db.cursor.execute(self.mysqldict.query['list_tables']) self.result = self.mysql_db.cursor.execute(self.mysqldict.query['list_tables'])
self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) # self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables'])
# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) # self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
def testMySQLRecreateTables(self): def testMySQLRecreateTables(self):
"""Test droping then recreating fpdb table schema""" """Test droping then recreating fpdb table schema"""
self.mysql_db.recreate_tables() self.mysql_db.recreate_tables()
self.result = self.mysql_db.cursor.execute("SHOW TABLES") self.result = self.mysql_db.cursor.execute("SHOW TABLES")
self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
def testImportHandHistoryFiles(self): def testPokerStarsHHDate(self):
"""Test import of single HH file""" latest = "PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]"
self.mysqlimporter.addImportFile("regression-test-files/hand-histories/ps-lhe-ring-3hands.txt") previous = "PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/08/17 - 01:14:43 (ET)"
self.mysqlimporter.runImport() older1 = "PokerStars Game #21969660557: Hold'em No Limit ($0.50/$1.00) - 2008/09/07 06:23:14 ET"
self.mysqlimporter.addImportDirectory("regression-test-files/hand-histories")
self.mysqlimporter.runImport()
# def testPostgresSQLRecreateTables(self): result = fpdb_simple.parseHandStartTime(older1, "ps")
# """Test droping then recreating fpdb table schema""" self.failUnless(result==datetime.datetime(2008,9,7,11,23,14),
# self.pg_db.recreate_tables() "Date incorrect, expected: 2008-09-07 11:23:14 got: " + str(result))
# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables']) result = fpdb_simple.parseHandStartTime(latest, "ps")
# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result)) self.failUnless(result==datetime.datetime(2008,11,12,15,00,48),
"Date incorrect, expected: 2008-11-12 15:00:48 got: " + str(result))
result = fpdb_simple.parseHandStartTime(previous, "ps")
self.failUnless(result==datetime.datetime(2008,8,17,6,14,43),
"Date incorrect, expected: 2008-08-17 01:14:43 got: " + str(result))
def testImportHandHistoryFiles(self):
"""Test import of single HH file"""
self.mysqlimporter.addImportFile("regression-test-files/hand-histories/ps-lhe-ring-3hands.txt")
self.mysqlimporter.runImport()
self.mysqlimporter.addImportDirectory("regression-test-files/hand-histories")
self.mysqlimporter.runImport()
# def testPostgresSQLRecreateTables(self):
# """Test droping then recreating fpdb table schema"""
# self.pg_db.recreate_tables()
# self.result = self.pg_db.cursor.execute(self.pgdict.query['list_tables'])
# self.failUnless(self.result==13, "Number of tables in database incorrect. Expected 13 got " + str(self.result))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -237,18 +237,80 @@ class Sql:
GROUP BY HudCache.PlayerId GROUP BY HudCache.PlayerId
""" """
# FROM HudCache, Hands # same as above except stats are aggregated for all blind/limit levels
# WHERE HudCache.PlayerId in self.query['get_stats_from_hand_aggregated'] = """
# (SELECT PlayerId FROM HandsPlayers SELECT HudCache.playerId AS player_id,
# WHERE handId = %s) sum(HDs) AS n,
# AND Hands.id = %s sum(street0VPI) AS vpip,
# AND Hands.gametypeId = HudCache.gametypeId sum(street0Aggr) AS pfr,
sum(street0_3B4BChance) AS TB_opp_0,
# AND PlayerId LIKE %s sum(street0_3B4BDone) AS TB_0,
# HudCache.gametypeId AS gametypeId, sum(street1Seen) AS saw_f,
# activeSeats AS n_active, sum(street1Seen) AS saw_1,
# position AS position, sum(street2Seen) AS saw_2,
# HudCache.tourneyTypeId AS tourneyTypeId, sum(street3Seen) AS saw_3,
sum(street4Seen) AS saw_4,
sum(sawShowdown) AS sd,
sum(street1Aggr) AS aggr_1,
sum(street2Aggr) AS aggr_2,
sum(street3Aggr) AS aggr_3,
sum(street4Aggr) AS aggr_4,
sum(otherRaisedStreet1) AS was_raised_1,
sum(otherRaisedStreet2) AS was_raised_2,
sum(otherRaisedStreet3) AS was_raised_3,
sum(otherRaisedStreet4) AS was_raised_4,
sum(foldToOtherRaisedStreet1) AS f_freq_1,
sum(foldToOtherRaisedStreet2) AS f_freq_2,
sum(foldToOtherRaisedStreet3) AS f_freq_3,
sum(foldToOtherRaisedStreet4) AS f_freq_4,
sum(wonWhenSeenStreet1) AS w_w_s_1,
sum(wonAtSD) AS wmsd,
sum(stealAttemptChance) AS steal_opp,
sum(stealAttempted) AS steal,
sum(foldSbToStealChance) AS SBstolen,
sum(foldedSbToSteal) AS SBnotDef,
sum(foldBbToStealChance) AS BBstolen,
sum(foldedBbToSteal) AS BBnotDef,
sum(street1CBChance) AS CB_opp_1,
sum(street1CBDone) AS CB_1,
sum(street2CBChance) AS CB_opp_2,
sum(street2CBDone) AS CB_2,
sum(street3CBChance) AS CB_opp_3,
sum(street3CBDone) AS CB_3,
sum(street4CBChance) AS CB_opp_4,
sum(street4CBDone) AS CB_4,
sum(foldToStreet1CBChance) AS f_cb_opp_1,
sum(foldToStreet1CBDone) AS f_cb_1,
sum(foldToStreet2CBChance) AS f_cb_opp_2,
sum(foldToStreet2CBDone) AS f_cb_2,
sum(foldToStreet3CBChance) AS f_cb_opp_3,
sum(foldToStreet3CBDone) AS f_cb_3,
sum(foldToStreet4CBChance) AS f_cb_opp_4,
sum(foldToStreet4CBDone) AS f_cb_4,
sum(totalProfit) AS net,
sum(street1CheckCallRaiseChance) AS ccr_opp_1,
sum(street1CheckCallRaiseDone) AS ccr_1,
sum(street2CheckCallRaiseChance) AS ccr_opp_2,
sum(street2CheckCallRaiseDone) AS ccr_2,
sum(street3CheckCallRaiseChance) AS ccr_opp_3,
sum(street3CheckCallRaiseDone) AS ccr_3,
sum(street4CheckCallRaiseChance) AS ccr_opp_4,
sum(street4CheckCallRaiseDone) AS ccr_4
FROM HudCache, Hands
WHERE HudCache.PlayerId in
(SELECT PlayerId FROM HandsPlayers
WHERE handId = %s)
AND Hands.id = %s
AND HudCache.gametypeId in
(SELECT gt1.id from Gametypes gt1, Gametypes gt2, Hands
WHERE gt1.siteid = gt2.siteid
AND gt1.type = gt2.type
AND gt1.category = gt2.category
AND gt1.limittype = gt2.limittype
AND gt2.id = Hands.gametypeId
AND Hands.id = %s)
GROUP BY HudCache.PlayerId
"""
self.query['get_players_from_hand'] = """ self.query['get_players_from_hand'] = """
SELECT HandsPlayers.playerId, seatNo, name SELECT HandsPlayers.playerId, seatNo, name
@ -288,15 +350,15 @@ class Sql:
order by seatNo order by seatNo
""" """
# self.query['get_hand_info'] = """ self.query['get_action_from_hand'] = """
# SELECT SELECT street, Players.name, HandsActions.action, HandsActions.amount, actionno
# game_id, FROM Players, HandsActions, HandsPlayers
# CONCAT(hole_card_1, hole_card_2, hole_card_3, hole_card_4, hole_card_5, hole_card_6, hole_card_7) AS hand, WHERE HandsActions.action != 'blind'
# total_won-total_bet AS net AND HandsPlayers.handid = %s
# FROM game_players AND HandsPlayers.playerid = Players.id
# WHERE game_id = %s AND player_id = 3 AND HandsActions.handPlayerId = HandsPlayers.id
# """ ORDER BY street, actionno
"""
if __name__== "__main__": if __name__== "__main__":
# just print the default queries and exit # just print the default queries and exit
s = Sql(game = 'razz', type = 'ptracks') s = Sql(game = 'razz', type = 'ptracks')

View File

@ -89,14 +89,14 @@ def vpip(stat_dict, player):
'v=%3.1f' % (100*stat) + '%', 'v=%3.1f' % (100*stat) + '%',
'vpip=%3.1f' % (100*stat) + '%', 'vpip=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']), '(%d/%d)' % (stat_dict[player]['vpip'], stat_dict[player]['n']),
'vpip' 'Voluntarily Put In Pot %'
) )
except: return (stat, except: return (stat,
'%3.1f' % (0) + '%', '%3.1f' % (0) + '%',
'w=%3.1f' % (0) + '%', 'v=%3.1f' % (0) + '%',
'wtsd=%3.1f' % (0) + '%', 'vpip=%3.1f' % (0) + '%',
'(%d/%d)' % (0, 0), '(%d/%d)' % (0, 0),
'wtsd' 'Voluntarily Put In Pot %'
) )
def vpip_0(stat_dict, player): def vpip_0(stat_dict, player):
@ -129,7 +129,7 @@ def pfr(stat_dict, player):
'p=%3.1f' % (100*stat) + '%', 'p=%3.1f' % (100*stat) + '%',
'pfr=%3.1f' % (100*stat) + '%', 'pfr=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']), '(%d/%d)' % (stat_dict[player]['pfr'], stat_dict[player]['n']),
'pfr' 'Pre-Flop Raise %'
) )
except: except:
return (stat, return (stat,
@ -137,7 +137,7 @@ def pfr(stat_dict, player):
'p=%3.1f' % (0) + '%', 'p=%3.1f' % (0) + '%',
'pfr=%3.1f' % (0) + '%', 'pfr=%3.1f' % (0) + '%',
'(%d/%d)' % (0, 0), '(%d/%d)' % (0, 0),
'pfr' 'Pre-Flop Raise %'
) )
def pfr_0(stat_dict, player): def pfr_0(stat_dict, player):
@ -214,7 +214,7 @@ def saw_f(stat_dict, player):
'sf=%3.1f' % (100*stat) + '%', 'sf=%3.1f' % (100*stat) + '%',
'saw_f=%3.1f' % (100*stat) + '%', 'saw_f=%3.1f' % (100*stat) + '%',
'(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']), '(%d/%d)' % (stat_dict[player]['saw_f'], stat_dict[player]['n']),
'saw_f' 'Flop Seen %'
) )
except: except:
stat = 0.0 stat = 0.0
@ -225,7 +225,7 @@ def saw_f(stat_dict, player):
'sf=%3.1f' % (stat) + '%', 'sf=%3.1f' % (stat) + '%',
'saw_f=%3.1f' % (stat) + '%', 'saw_f=%3.1f' % (stat) + '%',
'(%d/%d)' % (num, den), '(%d/%d)' % (num, den),
'saw_f' 'Flop Seen %'
) )
def n(stat_dict, player): def n(stat_dict, player):
@ -303,12 +303,11 @@ def f_SB_steal(stat_dict, player):
) )
except: except:
return (stat, return (stat,
'%3.1f' % (0) + '%', 'NA',
'fSB=%3.1f' % (0) + '%', 'fSB=NA',
'fSB_s=%3.1f' % (0) + '%', 'fSB_s=NA',
'(%d/%d)' % (0, 0), '0/0',
'% folded SB to steal' '% folded SB to steal')
)
def f_BB_steal(stat_dict, player): def f_BB_steal(stat_dict, player):
""" Folded BB to steal.""" """ Folded BB to steal."""
@ -324,12 +323,11 @@ def f_BB_steal(stat_dict, player):
) )
except: except:
return (stat, return (stat,
'%3.1f' % (0) + '%', 'NA',
'fBB=%3.1f' % (0) + '%', 'fBB=NA',
'fBB_s=%3.1f' % (0) + '%', 'fBB_s=NA',
'(%d/%d)' % (0, 0), '0/0',
'% folded BB to steal' '% folded BB to steal')
)
def three_B_0(stat_dict, player): def three_B_0(stat_dict, player):
""" Three bet preflop/3rd.""" """ Three bet preflop/3rd."""
@ -454,7 +452,7 @@ def a_freq_4(stat_dict, player):
'a4=%3.1f' % (0) + '%', 'a4=%3.1f' % (0) + '%',
'a_fq_4=%3.1f' % (0) + '%', 'a_fq_4=%3.1f' % (0) + '%',
'(%d/%d)' % (0, 0), '(%d/%d)' % (0, 0),
'Aggression Freq flop/4th' 'Aggression Freq 7th'
) )
def a_freq_123(stat_dict, player): def a_freq_123(stat_dict, player):

413
pyfpdb/Tables.py Normal file → Executable file
View File

@ -7,7 +7,6 @@ of Table_Window objects representing the windows found.
""" """
# Copyright 2008, Ray E. Barker # Copyright 2008, Ray E. Barker
#
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
@ -40,7 +39,35 @@ if os.name == 'nt':
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
# Each TableWindow object must have the following attributes correctly populated:
# tw.name = the table name from the title bar, which must to match the table name
# from the corresponding hand history.
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# format that the system presents it. This is Xid in Xwindows and
# hwnd in Microsoft Windows.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
class Table_Window: class Table_Window:
def __init__(self, info = {}):
if info.has_key('number'): self.number = info['number']
if info.has_key('exe'): self.exe = info['exe']
if info.has_key('width'): self.width = info['width']
if info.has_key('height'): self.height = info['height']
if info.has_key('x'): self.x = info['x']
if info.has_key('y'): self.y = info['y']
if info.has_key('site'): self.site = info['site']
if info.has_key('title'): self.title = info['title']
if info.has_key('name'): self.name = info['name']
def __str__(self): def __str__(self):
# __str__ method for testing # __str__ method for testing
temp = 'TableWindow object\n' temp = 'TableWindow object\n'
@ -51,13 +78,10 @@ class Table_Window:
temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table) temp = temp + " tournament = %d\n table = %d" % (self.tournament, self.table)
return temp return temp
def get_details(table): ############################################################################
table.game = 'razz' # Top-level discovery routines--these are the modules interface
table.max = 8
table.struture = 'limit'
table.tournament = 0
def discover(c): def discover(c):
"""Dispatch routine for finding all potential poker client windows."""
if os.name == 'posix': if os.name == 'posix':
tables = discover_posix(c) tables = discover_posix(c)
elif os.name == 'nt': elif os.name == 'nt':
@ -66,86 +90,99 @@ def discover(c):
tables = discover_mac(c) tables = discover_mac(c)
else: else:
tables = {} tables = {}
return tables
return(tables)
def discover_table_by_name(c, tablename): def discover_table_by_name(c, tablename):
"""Dispatch routine for finding poker client windows with the given name."""
if os.name == 'posix': if os.name == 'posix':
table = discover_posix_by_name(c, tablename) info = discover_posix_by_name(c, tablename)
elif os.name == 'nt': elif os.name == 'nt':
table = discover_nt_by_name(c, tablename) info = discover_nt_by_name(c, tablename)
elif os.name == 'mac': elif os.name == 'mac':
table = discover_mac_by_name(c, tablename) info = discover_mac_by_name(c, tablename)
else: else:
table = None return None
return(table) if info == None:
return None
return Table_Window(info)
def discover_tournament_table(c, tour_number, tab_number):
"""Dispatch routine for finding poker clients with tour and table number."""
if os.name == 'posix':
info = discover_posix_tournament(c, tour_number, tab_number)
elif os.name == 'nt':
info = discover_nt_tournament(c, tour_number, tab_number)
elif os.name == 'mac':
info = discover_mac_tournament(c, tour_number, tab_number)
else:
return None
if info:
return Table_Window(info)
return None
#############################################################################
# Posix (= XWindows) specific routines
def discover_posix(c): def discover_posix(c):
""" Poker client table window finder for posix/Linux = XWindows.""" """Poker client table window finder for posix/Linux = XWindows."""
tables = {} tables = {}
for listing in os.popen('xwininfo -root -tree').readlines(): for listing in os.popen('xwininfo -root -tree').readlines():
# xwininfo -root -tree -id 0xnnnnn gets the info on a single window # xwininfo -root -tree -id 0xnnnnn gets the info on a single window
if re.search('Lobby', listing): continue for s in c.get_supported_sites():
if re.search('Instant Hand History', listing): continue params = c.get_site_parameters(s)
if not re.search('Logged In as ', listing, re.IGNORECASE): continue if re.search(params['table_finder'], listing):
if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby if re.search('Lobby', listing): continue
for s in c.supported_sites.keys(): if re.search('Instant Hand History', listing): continue
if re.search(c.supported_sites[s].table_finder, listing): if re.search('\"Full Tilt Poker\"', listing): continue # FTP Lobby
mo = re.match('\s+([\dxabcdef]+) (.+):.+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) if re.search('History for table:', listing): continue
if mo.group(2) == '(has no name)': continue if re.search('has no name', listing): continue
if re.match('[\(\)\d\s]+', mo.group(2)): continue # this is a popup info = decode_xwininfo(c, listing)
tw = Table_Window() if info['site'] == None: continue
tw.site = c.supported_sites[s].site_name if info['title'] == info['exe']: continue
tw.number = int(mo.group(1), 0) # this appears to be a poker client, so make a table object for it
tw.title = mo.group(2) tw = Table_Window(info)
tw.width = int( mo.group(3) ) eval("%s(tw)" % params['decoder'])
tw.height = int( mo.group(4) )
tw.x = int (mo.group(5) )
tw.y = int (mo.group(6) )
tw.title = re.sub('\"', '', tw.title)
# use this eval thingie to call the title bar decoder specified in the config file
eval("%s(tw)" % c.supported_sites[s].decoder)
tables[tw.name] = tw tables[tw.name] = tw
return tables return tables
def discover_posix_by_name(c, tablename): def discover_posix_by_name(c, tablename):
tables = discover_posix(c) """Find an XWindows poker client of the given name."""
for t in tables: for listing in os.popen('xwininfo -root -tree').readlines():
if tables[t].name.find(tablename) > -1: if re.search(tablename, listing):
return tables[t] if re.search('History for table:', listing): continue
return None info = decode_xwininfo(c, listing)
if not info['name'] == tablename: continue
return info
return False
# def discover_posix_tournament(c, t_number, s_number):
# The discover_xx functions query the system and report on the poker clients """Finds the X window for a client, given tournament and table nos."""
# currently displayed on the screen. The discover_posix should give you search_string = "%s.+Table\s%s" % (t_number, s_number)
# some idea how to support other systems. for listing in os.popen('xwininfo -root -tree').readlines():
# if re.search(search_string, listing):
# discover_xx() returns a dict of TableWindow objects--one TableWindow return decode_xwininfo(c, listing)
# object for each poker client table on the screen. return False
#
# Each TableWindow object must have the following attributes correctly populated:
# tw.site = the site name, e.g. PokerStars, FullTilt. This must match the site
# name specified in the config file.
# tw.number = This is the system id number for the client table window in the
# format that the system presents it.
# tw.title = The full title from the window title bar.
# tw.width, tw.height = The width and height of the window in pixels. This is
# the internal width and height, not including the title bar and
# window borders.
# tw.x, tw.y = The x, y (horizontal, vertical) location of the window relative
# to the top left of the display screen. This also does not include the
# title bar and window borders. To put it another way, this is the
# screen location of (0, 0) in the working window.
def win_enum_handler(hwnd, titles): def decode_xwininfo(c, info_string):
titles[hwnd] = win32gui.GetWindowText(hwnd) """Gets window parameters from xwinifo string--XWindows."""
info = {}
mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', info_string)
def child_enum_handler(hwnd, children): if not mo:
print hwnd, win32.GetWindowRect(hwnd) return None
else:
info['number'] = int( mo.group(1), 0)
info['exe'] = mo.group(3)
info['width'] = int( mo.group(4) )
info['height'] = int( mo.group(5) )
info['x'] = int( mo.group(6) )
info['y'] = int( mo.group(7) )
info['site'] = get_site_from_exe(c, info['exe'])
info['title'] = re.sub('\"', '', mo.group(2))
title_bits = re.split(' - ', info['title'])
info['name'] = clean_title(title_bits[0])
return info
##############################################################################
# NT (= Windows) specific routines
def discover_nt(c): def discover_nt(c):
""" Poker client table window finder for Windows.""" """ Poker client table window finder for Windows."""
# #
@ -185,72 +222,88 @@ def discover_nt(c):
return tables return tables
def discover_nt_by_name(c, tablename): def discover_nt_by_name(c, tablename):
# this is pretty much identical to the 'search all windows for all poker sites' code, but made to dig just for a specific table name """Finds poker client window with the given table name."""
# it could be implemented a bunch better - and we need to not assume the width/height thing that (steffen?) assumed above, we should titles = {}
# be able to dig up the window's titlebar handle and get it's information, and such .. but.. for now, i guess this will work. win32gui.EnumWindows(win_enum_handler, titles)
# - eric for hwnd in titles.keys():
if titles[hwnd].find(tablename) == -1: continue
if titles[hwnd].find("History for table:") > -1: continue
if titles[hwnd].find("HUD:") > -1: continue
if titles[hwnd].find("Chat:") > -1: continue
return decode_windows(c, titles[hwnd], hwnd)
return False
def discover_nt_tournament(c, tour_number, tab_number):
"""Finds the Windows window handle for the given tournament/table."""
search_string = "%s.+%s" % (tour_number, tab_number)
titles ={}
win32gui.EnumWindows(win_enum_handler, titles)
for hwnd in titles.keys():
if re.search(search_string, titles[hwnd]):
return decode_windows(c, titles[hwnd], hwnd)
return False
def get_nt_exe(hwnd):
"""Finds the name of the executable that the given window handle belongs to."""
processid = win32process.GetWindowThreadProcessId(hwnd)
pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1])
return win32process.GetModuleFileNameEx(pshandle, 0)
def decode_windows(c, title, hwnd):
"""Gets window parameters from the window title and handle--Windows."""
# I cannot figure out how to get the inside dimensions of the poker table
# windows. So I just assume all borders are 3 thick and all title bars
# are 29 high. No doubt this will be off when used with certain themes.
b_width = 3 b_width = 3
tb_height = 29 tb_height = 29
titles = {}
# tables = discover_nt(c)
win32gui.EnumWindows(win_enum_handler, titles)
for s in c.supported_sites.keys():
for hwnd in titles.keys():
processid = win32process.GetWindowThreadProcessId(hwnd)
pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1])
exe = win32process.GetModuleFileNameEx(pshandle, 0)
if exe.find(c.supported_sites[s].table_finder) == -1:
continue
if titles[hwnd].find(tablename) > -1:
if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
continue
tw = Table_Window()
tw.number = hwnd
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
tw.title = titles[hwnd]
tw.width = int(width) - 2 * b_width
tw.height = int(height) - b_width - tb_height
tw.x = int(x) + b_width
tw.y = int(y) + tb_height
tw.site = c.supported_sites[s].site_name
if not tw.site == "Unknown" and not c.supported_sites[tw.site].decoder == "Unknown":
eval("%s(tw)" % c.supported_sites[tw.site].decoder)
else:
tw.name = tablename
return tw
# if we don't find anything by process name, let's search one more time, and call it Unknown ? info = {}
for hwnd in titles.keys(): info['number'] = hwnd
if titles[hwnd].find(tablename) > -1: info['title'] = re.sub('\"', '', title)
if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1: (x, y, width, height) = win32gui.GetWindowRect(hwnd)
continue
tw = Table_Window()
tw.number = hwnd
(x, y, width, height) = win32gui.GetWindowRect(hwnd)
tw.title = titles[hwnd]
tw.width = int(width) - 2 * b_width
tw.height = int(height) - b_width - tb_height
tw.x = int(x) + b_width
tw.y = int(y) + tb_height
tw.site = "Unknown"
tw.name = tablename
return tw
info['x'] = int(x) + b_width
info['y'] = int( y ) + tb_height
info['width'] = int( width ) - 2*b_width
info['height'] = int( height ) - b_width - tb_height
info['exe'] = get_nt_exe(hwnd)
title_bits = re.split(' - ', info['title'])
info['name'] = title_bits[0]
info['site'] = get_site_from_exe(c, info['exe'])
return info
def win_enum_handler(hwnd, titles):
titles[hwnd] = win32gui.GetWindowText(hwnd)
###################################################################
# Utility routines used by all the discoverers.
def get_site_from_exe(c, exe):
"""Look up the site from config, given the exe."""
for s in c.get_supported_sites():
params = c.get_site_parameters(s)
if re.search(params['table_finder'], exe):
return params['site_name']
return None return None
def discover_mac(c): def clean_title(name):
""" Poker client table window finder for Macintosh.""" """Clean the little info strings from the table name."""
tables = {} # these strings could go in a config file
return tables for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
def discover_mac_by_name(c, tablename): ' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
# again, i have no mac to test this on, sorry -eric ' no all-in', ' fast', ',', ' 50BB min', '\s+$']:
return discover_mac(c) name = re.sub(pattern, '', name)
name = name.rstrip()
return name
def pokerstars_decode_table(tw): def pokerstars_decode_table(tw):
# extract the table name OR the tournament number and table name from the title # Extract poker information from the window title. This is not needed for
# other info in title is redundant with data in the database # fpdb, since all that information is available in the db via new_hand_number.
# This is needed only when using the HUD with a backend less integrated.
title_bits = re.split(' - ', tw.title) title_bits = re.split(' - ', tw.title)
name = title_bits[0] name = title_bits[0]
mo = re.search('Tournament (\d+) Table (\d+)', name) mo = re.search('Tournament (\d+) Table (\d+)', name)
@ -260,12 +313,8 @@ def pokerstars_decode_table(tw):
tw.name = name tw.name = name
else: else:
tw.tournament = None tw.tournament = None
for pattern in [' no all-in', ' fast', ',', ' 50BB min']: tw.name = clean_title(name)
name = re.sub(pattern, '', name) mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball|Badugi)', tw.title)
name = re.sub('\s+$', '', name)
tw.name = name
mo = re.search('(Razz|Stud H/L|Stud|Omaha H/L|Omaha|Hold\'em|5-Card Draw|Triple Draw 2-7 Lowball)', tw.title)
tw.game = mo.group(1).lower() tw.game = mo.group(1).lower()
tw.game = re.sub('\'', '', tw.game) tw.game = re.sub('\'', '', tw.game)
@ -288,25 +337,103 @@ def pokerstars_decode_table(tw):
pass pass
def fulltilt_decode_table(tw): def fulltilt_decode_table(tw):
# extract the table name OR the tournament number and table name from the title # Extract poker information from the window title. This is not needed for
# other info in title is redundant with data in the database # fpdb, since all that information is available in the db via new_hand_number.
# This is needed only when using the HUD with a backend less integrated.
title_bits = re.split(' - ', tw.title) title_bits = re.split(' - ', tw.title)
name = title_bits[0] name = title_bits[0]
tw.tournament = None tw.tournament = None
for pattern in [' (6 max)', ' (heads up)', ' (deep)', tw.name = clean_title(name)
' (deep hu)', ' (deep 6)', ' (2)',
' (edu)', ' (edu, 6 max)', ' (6)' ]: def clean_title(name):
"""Clean the little info strings from the table name."""
# these strings could go in a config file
for pattern in [' \(6 max\)', ' \(heads up\)', ' \(deep\)',
' \(deep hu\)', ' \(deep 6\)', ' \(2\)',
' \(edu\)', ' \(edu, 6 max\)', ' \(6\)',
' no all-in', ' fast', ',', ' 50BB min', '\s+$']:
name = re.sub(pattern, '', name) name = re.sub(pattern, '', name)
# (tw.name, trash) = name.split(r' (', 1) name = name.rstrip()
tw.name = name.rstrip() return name
###########################################################################
# Mac specific routines....all stubs for now
def discover_mac_tournament(c, tour_number, tab_number):
"""Mac users need help."""
return None
def discover_mac(c):
"""Poker client table window finder for Macintosh."""
tables = {}
return tables
def discover_mac_by_name(c, tablename):
"""Oh, the humanity."""
# again, i have no mac to test this on, sorry -eric
return None
#def discover_nt_by_name(c, tablename):
# # this is pretty much identical to the 'search all windows for all poker sites' code, but made to dig just for a specific table name
# # it could be implemented a bunch better - and we need to not assume the width/height thing that (steffen?) assumed above, we should
# # be able to dig up the window's titlebar handle and get it's information, and such .. but.. for now, i guess this will work.
# # - eric
# b_width = 3
# tb_height = 29
# titles = {}
## tables = discover_nt(c)
# win32gui.EnumWindows(win_enum_handler, titles)
# for s in c.supported_sites.keys():
# for hwnd in titles.keys():
# processid = win32process.GetWindowThreadProcessId(hwnd)
# pshandle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, False, processid[1])
# exe = win32process.GetModuleFileNameEx(pshandle, 0)
# if exe.find(c.supported_sites[s].table_finder) == -1:
# continue
# if titles[hwnd].find(tablename) > -1:
# if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
# continue
# tw = Table_Window()
# tw.number = hwnd
# (x, y, width, height) = win32gui.GetWindowRect(hwnd)
# tw.title = titles[hwnd]
# tw.width = int(width) - 2 * b_width
# tw.height = int(height) - b_width - tb_height
# tw.x = int(x) + b_width
# tw.y = int(y) + tb_height
# tw.site = c.supported_sites[s].site_name
# if not tw.site == "Unknown" and not c.supported_sites[tw.site].decoder == "Unknown":
# eval("%s(tw)" % c.supported_sites[tw.site].decoder)
# else:
# tw.name = tablename
# return tw
#
# # if we don't find anything by process name, let's search one more time, and call it Unknown ?
# for hwnd in titles.keys():
# if titles[hwnd].find(tablename) > -1:
# if titles[hwnd].find("History for table:") > -1 or titles[hwnd].find("FPDBHUD") > -1:
# continue
# tw = Table_Window()
# tw.number = hwnd
# (x, y, width, height) = win32gui.GetWindowRect(hwnd)
# tw.title = titles[hwnd]
# tw.width = int(width) - 2 * b_width
# tw.height = int(height) - b_width - tb_height
# tw.x = int(x) + b_width
# tw.y = int(y) + tb_height
# tw.site = "Unknown"
# tw.name = tablename
# return tw
#
# return None
if __name__=="__main__": if __name__=="__main__":
c = Configuration.Config() c = Configuration.Config()
print discover_table_by_name(c, "Catacaos")
tables = discover(c)
print discover_table_by_name(c, "Howard Lederer")
print discover_tournament_table(c, "118942908", "3")
tables = discover(c)
for t in tables.keys(): for t in tables.keys():
print "t = ", t
print tables[t] print tables[t]
print "press enter to continue" print "press enter to continue"

View File

@ -22,13 +22,13 @@ from optparse import OptionParser
parser = OptionParser() parser = OptionParser()
parser.add_option("-x", "--errorsToConsole", action="store_true", parser.add_option("-x", "--errorsToConsole", action="store_true",
help="If passed error output will go to the console rather than .") help="If passed error output will go to the console rather than .")
(options, sys.argv) = parser.parse_args() (options, sys.argv) = parser.parse_args()
if not options.errorsToConsole: if not options.errorsToConsole:
print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_."
errorFile = open('fpdb-error-log.txt', 'w', 0) errorFile = open('fpdb-error-log.txt', 'w', 0)
sys.stderr = errorFile sys.stderr = errorFile
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
@ -37,6 +37,7 @@ import gtk
import fpdb_db import fpdb_db
import fpdb_simple import fpdb_simple
import GuiBulkImport import GuiBulkImport
import GuiPlayerStats
import GuiTableViewer import GuiTableViewer
import GuiAutoImport import GuiAutoImport
import GuiGraphViewer import GuiGraphViewer
@ -44,406 +45,417 @@ import FpdbSQLQueries
import Configuration import Configuration
class fpdb: class fpdb:
def tab_clicked(self, widget, tab_name): def tab_clicked(self, widget, tab_name):
"""called when a tab button is clicked to activate that tab""" """called when a tab button is clicked to activate that tab"""
#print "start of tab_clicked" #print "start of tab_clicked"
self.display_tab(tab_name) self.display_tab(tab_name)
#end def tab_clicked #end def tab_clicked
def add_and_display_tab(self, new_tab, new_tab_name): def add_and_display_tab(self, new_tab, new_tab_name):
"""just calls the component methods""" """just calls the component methods"""
self.add_tab(new_tab, new_tab_name) self.add_tab(new_tab, new_tab_name)
self.display_tab(new_tab_name) self.display_tab(new_tab_name)
#end def add_and_display_tab #end def add_and_display_tab
def add_tab(self, new_tab, new_tab_name): def add_tab(self, new_tab, new_tab_name):
"""adds a tab, namely creates the button and displays it and appends all the relevant arrays""" """adds a tab, namely creates the button and displays it and appends all the relevant arrays"""
#print "start of add_tab" #print "start of add_tab"
for i in self.tab_names: #todo: check this is valid for i in self.tab_names: #todo: check this is valid
if i==new_tab_name: if i==new_tab_name:
raise fpdb_simple.FpdbError("duplicate tab_name not permitted") raise fpdb_simple.FpdbError("duplicate tab_name not permitted")
self.tabs.append(new_tab) self.tabs.append(new_tab)
self.tab_names.append(new_tab_name) self.tab_names.append(new_tab_name)
new_tab_sel_button=gtk.ToggleButton(new_tab_name) new_tab_sel_button=gtk.ToggleButton(new_tab_name)
new_tab_sel_button.connect("clicked", self.tab_clicked, new_tab_name) new_tab_sel_button.connect("clicked", self.tab_clicked, new_tab_name)
self.tab_box.add(new_tab_sel_button) self.tab_box.add(new_tab_sel_button)
new_tab_sel_button.show() new_tab_sel_button.show()
self.tab_buttons.append(new_tab_sel_button) self.tab_buttons.append(new_tab_sel_button)
#end def add_tab #end def add_tab
def display_tab(self, new_tab_name): def display_tab(self, new_tab_name):
"""displays the indicated tab""" """displays the indicated tab"""
#print "start of display_tab, len(self.tab_names):",len(self.tab_names) #print "start of display_tab, len(self.tab_names):",len(self.tab_names)
tab_no=-1 tab_no=-1
#if len(self.tab_names)>1: #if len(self.tab_names)>1:
for i in range(len(self.tab_names)): for i in range(len(self.tab_names)):
#print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i] #print "display_tab, new_tab_name:",new_tab_name," self.tab_names[i]:", self.tab_names[i]
if (new_tab_name==self.tab_names[i]): if (new_tab_name==self.tab_names[i]):
tab_no=i tab_no=i
#self.tab_buttons[i].set_active(False) #self.tab_buttons[i].set_active(False)
#else: #else:
# tab_no=0 # tab_no=0
#current_tab_no=-1 #current_tab_no=-1
for i in range(len(self.tab_names)): for i in range(len(self.tab_names)):
if self.current_tab==self.tabs[i]: if self.current_tab==self.tabs[i]:
#self.tab_buttons[i].set_active(False) #self.tab_buttons[i].set_active(False)
pass pass
if tab_no==-1: if tab_no==-1:
raise fpdb_simple.FpdbError("invalid tab_no") raise fpdb_simple.FpdbError("invalid tab_no")
else: else:
self.main_vbox.remove(self.current_tab) self.main_vbox.remove(self.current_tab)
#self.current_tab.destroy() #self.current_tab.destroy()
self.current_tab=self.tabs[tab_no] self.current_tab=self.tabs[tab_no]
self.main_vbox.add(self.current_tab) self.main_vbox.add(self.current_tab)
self.tab_buttons[tab_no].set_active(True) self.tab_buttons[tab_no].set_active(True)
self.current_tab.show() self.current_tab.show()
#end def display_tab #end def display_tab
def delete_event(self, widget, event, data=None): def delete_event(self, widget, event, data=None):
return False return False
#end def delete_event #end def delete_event
def destroy(self, widget, data=None): def destroy(self, widget, data=None):
self.quit(widget, data) self.quit(widget, data)
#end def destroy #end def destroy
def dia_about(self, widget, data): def dia_about(self, widget, data):
print "todo: implement dia_about" print "todo: implement dia_about"
#end def dia_about #end def dia_about
def dia_create_del_database(self, widget, data): def dia_create_del_database(self, widget, data):
print "todo: implement dia_create_del_database" print "todo: implement dia_create_del_database"
obtain_global_lock() self.obtain_global_lock()
#end def dia_create_del_database #end def dia_create_del_database
def dia_create_del_user(self, widget, data): def dia_create_del_user(self, widget, data):
print "todo: implement dia_create_del_user" print "todo: implement dia_create_del_user"
obtain_global_lock() self.obtain_global_lock()
#end def dia_create_del_user #end def dia_create_del_user
def dia_database_stats(self, widget, data): def dia_database_stats(self, widget, data):
print "todo: implement dia_database_stats" print "todo: implement dia_database_stats"
#string=fpdb_db.getDbStats(db, cursor) #string=fpdb_db.getDbStats(db, cursor)
#end def dia_database_stats #end def dia_database_stats
def dia_delete_db_parts(self, widget, data): def dia_delete_db_parts(self, widget, data):
print "todo: implement dia_delete_db_parts" print "todo: implement dia_delete_db_parts"
obtain_global_lock() self.obtain_global_lock()
#end def dia_delete_db_parts #end def dia_delete_db_parts
def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None): def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None):
print "todo: implement dia_edit_profile" print "todo: implement dia_edit_profile"
obtain_global_lock() self.obtain_global_lock()
#end def dia_edit_profile #end def dia_edit_profile
def dia_export_db(self, widget, data): def dia_export_db(self, widget, data):
print "todo: implement dia_export_db" print "todo: implement dia_export_db"
obtain_global_lock() self.obtain_global_lock()
#end def dia_export_db #end def dia_export_db
def dia_get_db_root_credentials(self): def dia_get_db_root_credentials(self):
"""obtains db root credentials from user""" """obtains db root credentials from user"""
print "todo: implement dia_get_db_root_credentials" print "todo: implement dia_get_db_root_credentials"
# user, pw=None, None # user, pw=None, None
# #
# dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0, # dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0,
# buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK)) # buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK))
# #
# label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.") # label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.")
# #
# #
# label_user=gtk.Label("Username") # label_user=gtk.Label("Username")
# dialog.vbox.add(label_user) # dialog.vbox.add(label_user)
# label_user.show() # label_user.show()
# #
# response=dialog.run() # response=dialog.run()
# dialog.destroy() # dialog.destroy()
# return (user, pw, response) # return (user, pw, response)
#end def dia_get_db_root_credentials #end def dia_get_db_root_credentials
def dia_import_db(self, widget, data): def dia_import_db(self, widget, data):
print "todo: implement dia_import_db" print "todo: implement dia_import_db"
obtain_global_lock() self.obtain_global_lock()
#end def dia_import_db #end def dia_import_db
def dia_licensing(self, widget, data): def dia_licensing(self, widget, data):
print "todo: implement dia_licensing" print "todo: implement dia_licensing"
#end def dia_licensing #end def dia_licensing
def dia_load_profile(self, widget, data): def dia_load_profile(self, widget, data):
"""Dialogue to select a file to load a profile from""" """Dialogue to select a file to load a profile from"""
self.obtain_global_lock() self.obtain_global_lock()
chooser = gtk.FileChooserDialog(title="Please select a profile file to load", chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
action=gtk.FILE_CHOOSER_ACTION_OPEN, action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_filename(self.profile) chooser.set_filename(self.profile)
response = chooser.run() response = chooser.run()
chooser.destroy() chooser.destroy()
if response == gtk.RESPONSE_OK: if response == gtk.RESPONSE_OK:
self.load_profile(chooser.get_filename()) self.load_profile(chooser.get_filename())
elif response == gtk.RESPONSE_CANCEL: elif response == gtk.RESPONSE_CANCEL:
print 'User cancelled loading profile' print 'User cancelled loading profile'
#end def dia_load_profile #end def dia_load_profile
def dia_recreate_tables(self, widget, data): def dia_recreate_tables(self, widget, data):
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables""" """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
self.obtain_global_lock() self.obtain_global_lock()
dia_confirm=gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, dia_confirm=gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING,
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables") buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
diastring=("Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted.") diastring=("Please confirm that you want to (re-)create the tables. If there already are tables in the database "+self.db.database+" on "+self.db.host+" they will be deleted.")
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
response=dia_confirm.run() response=dia_confirm.run()
dia_confirm.destroy() dia_confirm.destroy()
if response == gtk.RESPONSE_YES: if response == gtk.RESPONSE_YES:
self.db.recreate_tables() self.db.recreate_tables()
elif response == gtk.RESPONSE_NO: elif response == gtk.RESPONSE_NO:
print 'User cancelled recreating tables' print 'User cancelled recreating tables'
#end def dia_recreate_tables #end def dia_recreate_tables
def dia_regression_test(self, widget, data): def dia_regression_test(self, widget, data):
print "todo: implement dia_regression_test" print "todo: implement dia_regression_test"
self.obtain_global_lock() self.obtain_global_lock()
#end def dia_regression_test #end def dia_regression_test
def dia_save_profile(self, widget, data): def dia_save_profile(self, widget, data):
print "todo: implement dia_save_profile" print "todo: implement dia_save_profile"
#end def dia_save_profile #end def dia_save_profile
def diaSetupWizard(self, path): def diaSetupWizard(self, path):
print "todo: implement setup wizard" print "todo: implement setup wizard"
print "setup wizard not implemented - please create the default configuration file:", path print "setup wizard not implemented - please create the default configuration file:", path
diaSetupWizard = gtk.Dialog(title="Fatal Error - Config File Missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) diaSetupWizard = gtk.Dialog(title="Fatal Error - Config File Missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK))
label = gtk.Label("Please copy the config file from the docs folder to:") label = gtk.Label("Please copy the config file from the docs folder to:")
diaSetupWizard.vbox.add(label) diaSetupWizard.vbox.add(label)
label.show() label.show()
label = gtk.Label(path) label = gtk.Label(path)
diaSetupWizard.vbox.add(label) diaSetupWizard.vbox.add(label)
label.show() label.show()
label = gtk.Label("and edit it according to the install documentation at http://fpdb.sourceforge.net") label = gtk.Label("and edit it according to the install documentation at http://fpdb.sourceforge.net")
diaSetupWizard.vbox.add(label) diaSetupWizard.vbox.add(label)
label.show() label.show()
response = diaSetupWizard.run() response = diaSetupWizard.run()
sys.exit(1) sys.exit(1)
#end def diaSetupWizard #end def diaSetupWizard
def get_menu(self, window): def get_menu(self, window):
"""returns the menu for this program""" """returns the menu for this program"""
accel_group = gtk.AccelGroup() accel_group = gtk.AccelGroup()
self.item_factory = gtk.ItemFactory(gtk.MenuBar, "<main>", accel_group) self.item_factory = gtk.ItemFactory(gtk.MenuBar, "<main>", accel_group)
self.item_factory.create_items(self.menu_items) self.item_factory.create_items(self.menu_items)
window.add_accel_group(accel_group) window.add_accel_group(accel_group)
return self.item_factory.get_widget("<main>") return self.item_factory.get_widget("<main>")
#end def get_menu #end def get_menu
def load_profile(self): def load_profile(self):
"""Loads profile from the provided path name.""" """Loads profile from the provided path name."""
self.settings = {} self.settings = {}
if (os.sep=="/"): if (os.sep=="/"):
self.settings['os']="linuxmac" self.settings['os']="linuxmac"
else: else:
self.settings['os']="windows" self.settings['os']="windows"
self.settings.update(self.config.get_db_parameters()) self.settings.update(self.config.get_db_parameters())
self.settings.update(self.config.get_tv_parameters()) self.settings.update(self.config.get_tv_parameters())
self.settings.update(self.config.get_import_parameters()) self.settings.update(self.config.get_import_parameters())
self.settings.update(self.config.get_default_paths()) self.settings.update(self.config.get_default_paths())
if self.db!=None: if self.db!=None:
self.db.disconnect() self.db.disconnect()
self.db = fpdb_db.fpdb_db() self.db = fpdb_db.fpdb_db()
#print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName'] #print "end of fpdb.load_profile, databaseName:",self.settings['db-databaseName']
self.db.connect(self.settings['db-backend'], self.settings['db-host'], self.settings['db-databaseName'], self.settings['db-user'], self.settings['db-password']) self.db.connect(self.settings['db-backend'],
if self.db.wrongDbVersion: self.settings['db-host'],
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) self.settings['db-databaseName'],
self.settings['db-user'],
self.settings['db-password'])
if self.db.wrongDbVersion:
diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
label = gtk.Label("An invalid DB version or missing tables have been detected.") label = gtk.Label("An invalid DB version or missing tables have been detected.")
diaDbVersionWarning.vbox.add(label) diaDbVersionWarning.vbox.add(label)
label.show() label.show()
label = gtk.Label("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu.") label = gtk.Label("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu.")
diaDbVersionWarning.vbox.add(label) diaDbVersionWarning.vbox.add(label)
label.show() label.show()
label = gtk.Label("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc.") label = gtk.Label("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc.")
diaDbVersionWarning.vbox.add(label) diaDbVersionWarning.vbox.add(label)
label.show() label.show()
response = diaDbVersionWarning.run() response = diaDbVersionWarning.run()
diaDbVersionWarning.destroy() diaDbVersionWarning.destroy()
# Database connected to successfully, load queries to pass on to other classes # Database connected to successfully, load queries to pass on to other classes
self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name()) self.querydict = FpdbSQLQueries.FpdbSQLQueries(self.db.get_backend_name())
#end def load_profile #end def load_profile
def not_implemented(self): def not_implemented(self):
print "todo: called unimplemented menu entry (users: pls ignore this)"#remove this once more entries are implemented print "todo: called unimplemented menu entry (users: pls ignore this)"#remove this once more entries are implemented
#end def not_implemented #end def not_implemented
def obtain_global_lock(self): def obtain_global_lock(self):
print "todo: implement obtain_global_lock (users: pls ignore this)" print "todo: implement obtain_global_lock (users: pls ignore this)"
#end def obtain_global_lock #end def obtain_global_lock
def quit(self, widget, data): def quit(self, widget, data):
print "Quitting normally" print "Quitting normally"
#check if current settings differ from profile, if so offer to save or abort #check if current settings differ from profile, if so offer to save or abort
self.db.disconnect() self.db.disconnect()
gtk.main_quit() gtk.main_quit()
#end def quit_cliecked #end def quit_cliecked
def release_global_lock(self): def release_global_lock(self):
print "todo: implement release_global_lock" print "todo: implement release_global_lock"
#end def release_global_lock #end def release_global_lock
def tab_abbreviations(self, widget, data): def tab_abbreviations(self, widget, data):
print "todo: implement tab_abbreviations" print "todo: implement tab_abbreviations"
#end def tab_abbreviations #end def tab_abbreviations
def tab_auto_import(self, widget, data): def tab_auto_import(self, widget, data):
"""opens the auto import tab""" """opens the auto import tab"""
new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings, self.config) new_aimp_thread=GuiAutoImport.GuiAutoImport(self.settings, self.config)
self.threads.append(new_aimp_thread) self.threads.append(new_aimp_thread)
aimp_tab=new_aimp_thread.get_vbox() aimp_tab=new_aimp_thread.get_vbox()
self.add_and_display_tab(aimp_tab, "Auto Import") self.add_and_display_tab(aimp_tab, "Auto Import")
#end def tab_auto_import #end def tab_auto_import
def tab_bulk_import(self, widget, data): def tab_bulk_import(self, widget, data):
"""opens a tab for bulk importing""" """opens a tab for bulk importing"""
#print "start of tab_bulk_import" #print "start of tab_bulk_import"
new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings, self.config) new_import_thread=GuiBulkImport.GuiBulkImport(self.db, self.settings, self.config)
self.threads.append(new_import_thread) self.threads.append(new_import_thread)
bulk_tab=new_import_thread.get_vbox() bulk_tab=new_import_thread.get_vbox()
self.add_and_display_tab(bulk_tab, "Bulk Import") self.add_and_display_tab(bulk_tab, "Bulk Import")
#end def tab_bulk_import #end def tab_bulk_import
def tab_main_help(self, widget, data): def tab_player_stats(self, widget, data):
"""Displays a tab with the main fpdb help screen""" new_ps_thread=GuiPlayerStats.GuiPlayerStats(self.db, self.config, self.querydict)
#print "start of tab_main_help" self.threads.append(new_ps_thread)
mh_tab=gtk.Label("""Welcome to Fpdb! ps_tab=new_ps_thread.get_vbox()
self.add_and_display_tab(ps_tab, "Player Stats")
def tab_main_help(self, widget, data):
"""Displays a tab with the main fpdb help screen"""
#print "start of tab_main_help"
mh_tab=gtk.Label("""Welcome to Fpdb!
For documentation please visit our website at http://fpdb.sourceforge.net/ or check the docs directory in the fpdb folder. For documentation please visit our website at http://fpdb.sourceforge.net/ or check the docs directory in the fpdb folder.
Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml
This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt")
self.add_and_display_tab(mh_tab, "Help") self.add_and_display_tab(mh_tab, "Help")
#end def tab_main_help #end def tab_main_help
def tab_table_viewer(self, widget, data): def tab_table_viewer(self, widget, data):
"""opens a table viewer tab""" """opens a table viewer tab"""
#print "start of tab_table_viewer" #print "start of tab_table_viewer"
new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings) new_tv_thread=GuiTableViewer.GuiTableViewer(self.db, self.settings)
self.threads.append(new_tv_thread) self.threads.append(new_tv_thread)
tv_tab=new_tv_thread.get_vbox() tv_tab=new_tv_thread.get_vbox()
self.add_and_display_tab(tv_tab, "Table Viewer") self.add_and_display_tab(tv_tab, "Table Viewer")
#end def tab_table_viewer #end def tab_table_viewer
def tabGraphViewer(self, widget, data): def tabGraphViewer(self, widget, data):
"""opens a graph viewer tab""" """opens a graph viewer tab"""
#print "start of tabGraphViewer" #print "start of tabGraphViewer"
new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config) new_gv_thread=GuiGraphViewer.GuiGraphViewer(self.db, self.settings, self.querydict, self.config)
self.threads.append(new_gv_thread) self.threads.append(new_gv_thread)
gv_tab=new_gv_thread.get_vbox() gv_tab=new_gv_thread.get_vbox()
self.add_and_display_tab(gv_tab, "Graphs") self.add_and_display_tab(gv_tab, "Graphs")
#end def tabGraphViewer #end def tabGraphViewer
def __init__(self): def __init__(self):
self.threads=[] self.threads=[]
self.db=None self.db=None
self.config = Configuration.Config() self.config = Configuration.Config()
self.load_profile() self.load_profile()
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", self.delete_event) self.window.connect("delete_event", self.delete_event)
self.window.connect("destroy", self.destroy) self.window.connect("destroy", self.destroy)
self.window.set_title("Free Poker DB - version: alpha9+, p143 or higher") self.window.set_title("Free Poker DB - version: alpha9+, p143 or higher")
self.window.set_border_width(1) self.window.set_border_width(1)
self.window.set_size_request(1020,400) self.window.set_size_request(1020,400)
self.window.set_resizable(True) self.window.set_resizable(True)
self.menu_items = ( self.menu_items = (
( "/_Main", None, None, 0, "<Branch>" ), ( "/_Main", None, None, 0, "<Branch>" ),
( "/Main/_Load Profile (broken)", "<control>L", self.dia_load_profile, 0, None ), ( "/Main/_Load Profile (broken)", "<control>L", self.dia_load_profile, 0, None ),
( "/Main/_Edit Profile (todo)", "<control>E", self.dia_edit_profile, 0, None ), ( "/Main/_Edit Profile (todo)", "<control>E", self.dia_edit_profile, 0, None ),
( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ), ( "/Main/_Save Profile (todo)", None, self.dia_save_profile, 0, None ),
("/Main/sep1", None, None, 0, "<Separator>" ), ("/Main/sep1", None, None, 0, "<Separator>" ),
("/Main/_Quit", "<control>Q", self.quit, 0, None ), ("/Main/_Quit", "<control>Q", self.quit, 0, None ),
("/_Import", None, None, 0, "<Branch>" ), ("/_Import", None, None, 0, "<Branch>" ),
("/Import/_Bulk Import", "<control>B", self.tab_bulk_import, 0, None ), ("/Import/_Bulk Import", "<control>B", self.tab_bulk_import, 0, None ),
("/Import/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ), ("/Import/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ),
("/Import/Auto _Rating (todo)", "<control>R", self.not_implemented, 0, None ), ("/Import/Auto _Rating (todo)", "<control>R", self.not_implemented, 0, None ),
("/_Viewers", None, None, 0, "<Branch>" ), ("/_Viewers", None, None, 0, "<Branch>" ),
("/_Viewers/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ), ("/_Viewers/_Auto Import and HUD", "<control>A", self.tab_auto_import, 0, None ),
("/Viewers/_Graphs", "<control>G", self.tabGraphViewer, 0, None ), ("/Viewers/_Graphs", "<control>G", self.tabGraphViewer, 0, None ),
("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Hand _Replayer (todo)", None, self.not_implemented, 0, None ),
("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Player _Details (todo)", None, self.not_implemented, 0, None ),
("/Viewers/_Player Stats (tabulated view) (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Player Stats (tabulated view)", None, self.tab_player_stats, 0, None ),
("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ), ("/Viewers/Starting _Hands (todo)", None, self.not_implemented, 0, None ),
("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ), ("/Viewers/_Session Replayer (todo)", None, self.not_implemented, 0, None ),
("/Viewers/Poker_table Viewer (mostly obselete)", "<control>T", self.tab_table_viewer, 0, None ), ("/Viewers/Poker_table Viewer (mostly obselete)", "<control>T", self.tab_table_viewer, 0, None ),
#( "/Viewers/Tourney Replayer #( "/Viewers/Tourney Replayer
( "/_Database", None, None, 0, "<Branch>" ), ( "/_Database", None, None, 0, "<Branch>" ),
( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ), ( "/Database/Create or Delete _Database (todo)", None, self.dia_create_del_database, 0, None ),
( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ), ( "/Database/Create or Delete _User (todo)", None, self.dia_create_del_user, 0, None ),
( "/Database/Create or Recreate _Tables", None, self.dia_recreate_tables, 0, None ), ( "/Database/Create or Recreate _Tables", None, self.dia_recreate_tables, 0, None ),
( "/Database/_Statistics (todo)", None, self.dia_database_stats, 0, None ), ( "/Database/_Statistics (todo)", None, self.dia_database_stats, 0, None ),
( "/D_ebugging", None, None, 0, "<Branch>" ), ( "/D_ebugging", None, None, 0, "<Branch>" ),
( "/Debugging/_Delete Parts of Database (todo)", None, self.dia_delete_db_parts, 0, None ), ( "/Debugging/_Delete Parts of Database (todo)", None, self.dia_delete_db_parts, 0, None ),
( "/Debugging/_Export DB (todo)", None, self.dia_export_db, 0, None ), ( "/Debugging/_Export DB (todo)", None, self.dia_export_db, 0, None ),
( "/Debugging/_Import DB (todo)", None, self.dia_import_db, 0, None ), ( "/Debugging/_Import DB (todo)", None, self.dia_import_db, 0, None ),
( "/Debugging/_Regression test (todo)", None, self.dia_regression_test, 0, None ), ( "/Debugging/_Regression test (todo)", None, self.dia_regression_test, 0, None ),
( "/_Help", None, None, 0, "<LastBranch>" ), ( "/_Help", None, None, 0, "<LastBranch>" ),
( "/Help/_Main Help", "<control>H", self.tab_main_help, 0, None ), ( "/Help/_Main Help", "<control>H", self.tab_main_help, 0, None ),
( "/Help/_Abbrevations (todo)", None, self.tab_abbreviations, 0, None ), ( "/Help/_Abbrevations (todo)", None, self.tab_abbreviations, 0, None ),
( "/Help/sep1", None, None, 0, "<Separator>" ), ( "/Help/sep1", None, None, 0, "<Separator>" ),
( "/Help/A_bout (todo)", None, self.dia_about, 0, None ), ( "/Help/A_bout (todo)", None, self.dia_about, 0, None ),
( "/Help/_License and Copying (todo)", None, self.dia_licensing, 0, None ) ( "/Help/_License and Copying (todo)", None, self.dia_licensing, 0, None )
) )
self.main_vbox = gtk.VBox(False, 1) self.main_vbox = gtk.VBox(False, 1)
self.main_vbox.set_border_width(1) self.main_vbox.set_border_width(1)
self.window.add(self.main_vbox) self.window.add(self.main_vbox)
self.main_vbox.show() self.main_vbox.show()
menubar = self.get_menu(self.window) menubar = self.get_menu(self.window)
self.main_vbox.pack_start(menubar, False, True, 0) self.main_vbox.pack_start(menubar, False, True, 0)
menubar.show() menubar.show()
#done menubar #done menubar
self.tabs=[] self.tabs=[]
self.tab_names=[] self.tab_names=[]
self.tab_buttons=[] self.tab_buttons=[]
self.tab_box = gtk.HBox(False,1) self.tab_box = gtk.HBox(False,1)
self.main_vbox.pack_start(self.tab_box, False, True, 0) self.main_vbox.pack_start(self.tab_box, False, True, 0)
self.tab_box.show() self.tab_box.show()
#done tab bar #done tab bar
self.current_tab = gtk.VBox(False,1) self.current_tab = gtk.VBox(False,1)
self.current_tab.set_border_width(1) self.current_tab.set_border_width(1)
self.main_vbox.add(self.current_tab) self.main_vbox.add(self.current_tab)
self.current_tab.show() self.current_tab.show()
self.tab_main_help(None, None) self.tab_main_help(None, None)
self.status_bar = gtk.Label("Status: Connected to "+self.db.get_backend_name()+" database named "+self.db.database+" on host "+self.db.host) self.status_bar = gtk.Label("Status: Connected to "+self.db.get_backend_name()+" database named "+self.db.database+" on host "+self.db.host)
self.main_vbox.pack_end(self.status_bar, False, True, 0) self.main_vbox.pack_end(self.status_bar, False, True, 0)
self.status_bar.show() self.status_bar.show()
self.window.show() self.window.show()
#end def __init__ #end def __init__
def main(self): def main(self):
gtk.main() gtk.main()
return 0 return 0
#end def main #end def main
if __name__ == "__main__": if __name__ == "__main__":
me = fpdb() me = fpdb()
me.main() me.main()

View File

@ -31,26 +31,39 @@ class fpdb_db:
self.SQLITE=4 self.SQLITE=4
#end def __init__ #end def __init__
def connect(self, backend, host, database, user, password): def connect(self, backend=None, host=None, database=None,
user=None, password=None):
"""Connects a database with the given parameters""" """Connects a database with the given parameters"""
if backend is None:
raise FpdbError('Database backend not defined')
self.backend=backend self.backend=backend
self.host=host self.host=host
self.database=database
self.user=user self.user=user
self.password=password self.password=password
self.database=database
if backend==self.MYSQL_INNODB: if backend==self.MYSQL_INNODB:
import MySQLdb import MySQLdb
self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database) self.db=MySQLdb.connect(host = host, user = user, passwd = password, db = database)
elif backend==self.PGSQL: elif backend==self.PGSQL:
import psycopg2 import psycopg2
self.db = psycopg2.connect(host = host, user = user, password = password, database = database) # If DB connection is made over TCP, then the variables
# host, user and password are required
if self.host or self.user:
self.db = psycopg2.connect(host = host,
user = user,
password = password,
database = database)
# For local domain-socket connections, only DB name is
# needed, and everything else is in fact undefined and/or
# flat out wrong
else:
self.db = psycopg2.connect(database = database)
else: else:
raise fpdb_simple.FpdbError("unrecognised database backend:"+backend) raise fpdb_simple.FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor() self.cursor=self.db.cursor()
self.cursor.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED') self.cursor.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED')
# Set up query dictionary as early in the connection process as we can. # Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
self.wrongDbVersion=False self.wrongDbVersion=False
try: try:
self.cursor.execute("SELECT * FROM Settings") self.cursor.execute("SELECT * FROM Settings")
@ -82,23 +95,23 @@ class fpdb_db:
def create_tables(self): def create_tables(self):
#todo: should detect and fail gracefully if tables already exist. #todo: should detect and fail gracefully if tables already exist.
self.cursor.execute(self.sql.query['createSettingsTable']) self.cursor.execute(self.sql.query['createSettingsTable'])
self.cursor.execute(self.sql.query['createSitesTable']) self.cursor.execute(self.sql.query['createSitesTable'])
self.cursor.execute(self.sql.query['createGametypesTable']) self.cursor.execute(self.sql.query['createGametypesTable'])
self.cursor.execute(self.sql.query['createPlayersTable']) self.cursor.execute(self.sql.query['createPlayersTable'])
self.cursor.execute(self.sql.query['createAutoratesTable']) self.cursor.execute(self.sql.query['createAutoratesTable'])
self.cursor.execute(self.sql.query['createHandsTable']) self.cursor.execute(self.sql.query['createHandsTable'])
self.cursor.execute(self.sql.query['createBoardCardsTable']) self.cursor.execute(self.sql.query['createBoardCardsTable'])
self.cursor.execute(self.sql.query['createTourneyTypesTable']) self.cursor.execute(self.sql.query['createTourneyTypesTable'])
self.cursor.execute(self.sql.query['createTourneysTable']) self.cursor.execute(self.sql.query['createTourneysTable'])
self.cursor.execute(self.sql.query['createTourneysPlayersTable']) self.cursor.execute(self.sql.query['createTourneysPlayersTable'])
self.cursor.execute(self.sql.query['createHandsPlayersTable']) self.cursor.execute(self.sql.query['createHandsPlayersTable'])
self.cursor.execute(self.sql.query['createHandsActionsTable']) self.cursor.execute(self.sql.query['createHandsActionsTable'])
self.cursor.execute(self.sql.query['createHudCacheTable']) self.cursor.execute(self.sql.query['createHudCacheTable'])
self.cursor.execute(self.sql.query['addTourneyIndex']) self.cursor.execute(self.sql.query['addTourneyIndex'])
self.cursor.execute(self.sql.query['addHandsIndex']) self.cursor.execute(self.sql.query['addHandsIndex'])
self.cursor.execute(self.sql.query['addPlayersIndex']) self.cursor.execute(self.sql.query['addPlayersIndex'])
self.fillDefaultData() self.fillDefaultData()
self.db.commit() self.db.commit()
#end def disconnect #end def disconnect
def drop_tables(self): def drop_tables(self):
@ -106,23 +119,23 @@ class fpdb_db:
if(self.get_backend_name() == 'MySQL InnoDB'): if(self.get_backend_name() == 'MySQL InnoDB'):
#Databases with FOREIGN KEY support need this switched of before you can drop tables #Databases with FOREIGN KEY support need this switched of before you can drop tables
self.drop_referencial_integrity() self.drop_referencial_integrity()
# Query the DB to see what tables exist # Query the DB to see what tables exist
self.cursor.execute(self.sql.query['list_tables']) self.cursor.execute(self.sql.query['list_tables'])
for table in self.cursor: for table in self.cursor:
self.cursor.execute(self.sql.query['drop_table'] + table[0]) self.cursor.execute(self.sql.query['drop_table'] + table[0])
elif(self.get_backend_name() == 'PostgreSQL'): elif(self.get_backend_name() == 'PostgreSQL'):
self.db.commit()# I have no idea why this makes the query work--REB 07OCT2008 self.db.commit()# I have no idea why this makes the query work--REB 07OCT2008
self.cursor.execute(self.sql.query['list_tables']) self.cursor.execute(self.sql.query['list_tables'])
tables = self.cursor.fetchall() tables = self.cursor.fetchall()
for table in tables: for table in tables:
self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade') self.cursor.execute(self.sql.query['drop_table'] + table[0] + ' cascade')
elif(self.get_backend_name() == 'SQLite'): elif(self.get_backend_name() == 'SQLite'):
#todo: sqlite version here #todo: sqlite version here
print "Empty function here" print "Empty function here"
self.db.commit() self.db.commit()
#end def drop_tables #end def drop_tables
def drop_referencial_integrity(self): def drop_referencial_integrity(self):

View File

@ -20,17 +20,18 @@
import sys import sys
try: try:
import MySQLdb import MySQLdb
mysqlLibFound=True mysqlLibFound=True
except: except:
pass pass
try: try:
import psycopg2 import psycopg2
pgsqlLibFound=True pgsqlLibFound=True
except: except:
pass pass
import traceback
import math import math
import os import os
import datetime import datetime
@ -41,270 +42,281 @@ from time import time
class Importer: class Importer:
def __init__(self, caller, settings, config): def __init__(self, caller, settings, config):
"""Constructor""" """Constructor"""
self.settings=settings self.settings=settings
self.caller=caller self.caller=caller
self.config = config self.config = config
self.db = None self.db = None
self.cursor = None self.cursor = None
self.filelist = {} self.filelist = {}
self.dirlist = {} self.dirlist = {}
self.monitor = False self.monitor = False
self.updated = {} #Time last import was run {file:mtime} self.updated = {} #Time last import was run {file:mtime}
self.lines = None self.lines = None
self.faobs = None #File as one big string self.faobs = None #File as one big string
self.pos_in_file = {} # dict to remember how far we have read in the file self.pos_in_file = {} # dict to remember how far we have read in the file
#Set defaults #Set defaults
self.callHud = self.config.get_import_parameters().get("callFpdbHud") self.callHud = self.config.get_import_parameters().get("callFpdbHud")
if not self.settings.has_key('minPrint'): if not self.settings.has_key('minPrint'):
self.settings['minPrint'] = 30 self.settings['minPrint'] = 30
self.dbConnect() self.dbConnect()
def dbConnect(self): # XXX: Why is this here, when fpdb_db.connect() already does the
#connect to DB # same?
if self.settings['db-backend'] == 2: def dbConnect(self):
if not mysqlLibFound: #connect to DB
raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file") if self.settings['db-backend'] == 2:
self.db = MySQLdb.connect(self.settings['db-host'], self.settings['db-user'], if not mysqlLibFound:
self.settings['db-password'], self.settings['db-databaseName']) raise fpdb_simple.FpdbError("interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file")
elif self.settings['db-backend'] == 3: self.db = MySQLdb.connect(self.settings['db-host'], self.settings['db-user'],
if not pgsqlLibFound: self.settings['db-password'], self.settings['db-databaseName'])
raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file") elif self.settings['db-backend'] == 3:
print self.settings if not pgsqlLibFound:
self.db = psycopg2.connect(host = self.settings['db-host'], raise fpdb_simple.FpdbError("interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file")
user = self.settings['db-user'], print self.settings
password = self.settings['db-password'], if not self.settings.has_key('db-host') or \
database = self.settings['db-databaseName']) not self.settings.has_key('db-user'):
elif self.settings['db-backend'] == 4: self.db = psycopg2.connect(host = self.settings['db-host'],
pass user = self.settings['db-user'],
else: password = self.settings['db-password'],
pass database = self.settings['db-databaseName'])
self.cursor = self.db.cursor() else:
dbname = self.settings['db-databaseName']
self.db = psycopg2.connect(database = dbname)
elif self.settings['db-backend'] == 4:
pass
else:
pass
self.cursor = self.db.cursor()
#Set functions #Set functions
def setCallHud(self, value): def setCallHud(self, value):
self.callHud = value self.callHud = value
def setMinPrint(self, value): def setMinPrint(self, value):
self.settings['minPrint'] = int(value) self.settings['minPrint'] = int(value)
def setHandCount(self, value): def setHandCount(self, value):
self.settings['handCount'] = int(value) self.settings['handCount'] = int(value)
def setQuiet(self, value): def setQuiet(self, value):
self.settings['quiet'] = value self.settings['quiet'] = value
def setFailOnError(self, value): def setFailOnError(self, value):
self.settings['failOnError'] = value self.settings['failOnError'] = value
# def setWatchTime(self): # def setWatchTime(self):
# self.updated = time() # self.updated = time()
def clearFileList(self): def clearFileList(self):
self.filelist = {} self.filelist = {}
#Add an individual file to filelist #Add an individual file to filelist
def addImportFile(self, filename, site = "default", filter = "passthrough"): def addImportFile(self, filename, site = "default", filter = "passthrough"):
#TODO: test it is a valid file #TODO: test it is a valid file
self.filelist[filename] = [site] + [filter] self.filelist[filename] = [site] + [filter]
#Add a directory of files to filelist #Add a directory of files to filelist
#Only one import directory per site supported. #Only one import directory per site supported.
#dirlist is a hash of lists: #dirlist is a hash of lists:
#dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] } #dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] }
def addImportDirectory(self, dir, monitor = False, site = "default", filter = "passthrough"): def addImportDirectory(self,dir,monitor = False, site = "default", filter = "passthrough"):
if dir != "/dev/null" and dir.lower() != "none": if os.path.isdir(dir):
if os.path.isdir(dir): if monitor == True:
if monitor == True: self.monitor = True
self.monitor = True self.dirlist[site] = [dir] + [filter]
self.dirlist[site] = [dir] + [filter]
for file in os.listdir(dir): for file in os.listdir(dir):
self.addImportFile(os.path.join(dir, file), site, filter) self.addImportFile(os.path.join(dir, file), site, filter)
else: else:
print "Warning: Attempted to add: '" + str(dir) + "' as an import directory\n" print "Warning: Attempted to add: '" + str(dir) + "' as an import directory"
#Run full import on filelist #Run full import on filelist
def runImport(self): def runImport(self):
for file in self.filelist: for file in self.filelist:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
#Run import on updated files, then store latest update time. #Run import on updated files, then store latest update time.
def runUpdated(self): def runUpdated(self):
#Check for new files in directory #Check for new files in directory
#todo: make efficient - always checks for new file, should be able to use mtime of directory #todo: make efficient - always checks for new file, should be able to use mtime of directory
# ^^ May not work on windows # ^^ May not work on windows
for site in self.dirlist: for site in self.dirlist:
self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1]) self.addImportDirectory(self.dirlist[site][0], False, site, self.dirlist[site][1])
for file in self.filelist: for file in self.filelist:
stat_info = os.stat(file) stat_info = os.stat(file)
try: try:
lastupdate = self.updated[file] lastupdate = self.updated[file]
if stat_info.st_mtime > lastupdate: if stat_info.st_mtime > lastupdate:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
self.updated[file] = time() self.updated[file] = time()
except: except:
self.updated[file] = time() self.updated[file] = time()
# This codepath only runs first time the file is found, if modified in the last # This codepath only runs first time the file is found, if modified in the last
# minute run an immediate import. # minute run an immediate import.
if (time() - stat_info.st_mtime) < 60: if (time() - stat_info.st_mtime) < 60:
self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1]) self.import_file_dict(file, self.filelist[file][0], self.filelist[file][1])
# This is now an internal function that should not be called directly. # This is now an internal function that should not be called directly.
def import_file_dict(self, file, site, filter): def import_file_dict(self, file, site, filter):
if(filter == "passthrough"): if(filter == "passthrough"):
self.import_fpdb_file(file, site) self.import_fpdb_file(file, site)
else: else:
#Load filter, and run filtered file though main importer #Load filter, and run filtered file though main importer
self.import_fpdb_file(file, site) self.import_fpdb_file(file, site)
def import_fpdb_file(self, file, site): def import_fpdb_file(self, file, site):
starttime = time() starttime = time()
last_read_hand=0 last_read_hand=0
loc = 0 loc = 0
if (file=="stdin"): if (file=="stdin"):
inputFile=sys.stdin inputFile=sys.stdin
else: else:
inputFile=open(file, "rU") inputFile=open(file, "rU")
try: loc = self.pos_in_file[file] try: loc = self.pos_in_file[file]
except: pass except: pass
# Read input file into class and close file # Read input file into class and close file
inputFile.seek(loc) inputFile.seek(loc)
self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines()) self.lines=fpdb_simple.removeTrailingEOL(inputFile.readlines())
self.pos_in_file[file] = inputFile.tell() self.pos_in_file[file] = inputFile.tell()
inputFile.close() inputFile.close()
try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return. try: # sometimes we seem to be getting an empty self.lines, in which case, we just want to return.
firstline = self.lines[0] firstline = self.lines[0]
except: except:
# print "import_fpdb_file", file, site, self.lines, "\n" # print "import_fpdb_file", file, site, self.lines, "\n"
return return
if firstline.find("Tournament Summary")!=-1: if firstline.find("Tournament Summary")!=-1:
print "TODO: implement importing tournament summaries" print "TODO: implement importing tournament summaries"
#self.faobs = readfile(inputFile) #self.faobs = readfile(inputFile)
#self.parseTourneyHistory() #self.parseTourneyHistory()
return 0 return 0
site=fpdb_simple.recogniseSite(firstline) site=fpdb_simple.recogniseSite(firstline)
category=fpdb_simple.recogniseCategory(firstline) category=fpdb_simple.recogniseCategory(firstline)
startpos=0 startpos=0
stored=0 #counter stored=0 #counter
duplicates=0 #counter duplicates=0 #counter
partial=0 #counter partial=0 #counter
errors=0 #counter errors=0 #counter
for i in range (len(self.lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method for i in range (len(self.lines)): #main loop, iterates through the lines of a file and calls the appropriate parser method
if (len(self.lines[i])<2): if (len(self.lines[i])<2):
endpos=i endpos=i
hand=self.lines[startpos:endpos] hand=self.lines[startpos:endpos]
if (len(hand[0])<2): if (len(hand[0])<2):
hand=hand[1:] hand=hand[1:]
cancelled=False cancelled=False
damaged=False damaged=False
if (site=="ftp"): if (site=="ftp"):
for i in range (len(hand)): for i in range (len(hand)):
if (hand[i].endswith(" has been canceled")): #this is their typo. this is a typo, right? if (hand[i].endswith(" has been canceled")): #this is their typo. this is a typo, right?
cancelled=True cancelled=True
seat1=hand[i].find("Seat ") #todo: make this recover by skipping this line seat1=hand[i].find("Seat ") #todo: make this recover by skipping this line
if (seat1!=-1): if (seat1!=-1):
if (hand[i].find("Seat ", seat1+3)!=-1): if (hand[i].find("Seat ", seat1+3)!=-1):
damaged=True damaged=True
if (len(hand)<3): if (len(hand)<3):
pass pass
#todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work. #todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work.
elif (hand[0].endswith(" (partial)")): #partial hand - do nothing elif (hand[0].endswith(" (partial)")): #partial hand - do nothing
partial+=1 partial+=1
elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and? elif (hand[1].find("Seat")==-1 and hand[2].find("Seat")==-1 and hand[3].find("Seat")==-1):#todo: should this be or instead of and?
partial+=1 partial+=1
elif (cancelled or damaged): elif (cancelled or damaged):
partial+=1 partial+=1
else: #normal processing else: #normal processing
isTourney=fpdb_simple.isTourney(hand[0]) isTourney=fpdb_simple.isTourney(hand[0])
if not isTourney: if not isTourney:
fpdb_simple.filterAnteBlindFold(site,hand) fpdb_simple.filterAnteBlindFold(site,hand)
hand=fpdb_simple.filterCrap(site, hand, isTourney) hand=fpdb_simple.filterCrap(site, hand, isTourney)
self.hand=hand self.hand=hand
try: try:
handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand) handsId=fpdb_parse_logic.mainParser(self.db, self.cursor, site, category, hand)
self.db.commit() self.db.commit()
stored+=1 stored+=1
self.db.commit() self.db.commit()
if self.callHud: if self.callHud:
#print "call to HUD here. handsId:",handsId #print "call to HUD here. handsId:",handsId
#pipe the Hands.id out to the HUD #pipe the Hands.id out to the HUD
self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep) self.caller.pipe_to_hud.stdin.write("%s" % (handsId) + os.linesep)
except fpdb_simple.DuplicateError: except fpdb_simple.DuplicateError:
duplicates+=1 duplicates+=1
except (ValueError), fe: except (ValueError), fe:
errors+=1 errors+=1
self.printEmailErrorMessage(errors, file, hand[0]) self.printEmailErrorMessage(errors, file, hand)
if (self.settings['failOnError']): if (self.settings['failOnError']):
self.db.commit() #dont remove this, in case hand processing was cancelled. self.db.commit() #dont remove this, in case hand processing was cancelled.
raise raise
except (fpdb_simple.FpdbError), fe: except (fpdb_simple.FpdbError), fe:
errors+=1 errors+=1
self.printEmailErrorMessage(errors, file, hand[0]) self.printEmailErrorMessage(errors, file, hand)
#fe.printStackTrace() #todo: get stacktrace #fe.printStackTrace() #todo: get stacktrace
self.db.rollback() self.db.rollback()
if (self.settings['failOnError']): if (self.settings['failOnError']):
self.db.commit() #dont remove this, in case hand processing was cancelled. self.db.commit() #dont remove this, in case hand processing was cancelled.
raise raise
if (self.settings['minPrint']!=0): if (self.settings['minPrint']!=0):
if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0): if ((stored+duplicates+partial+errors)%self.settings['minPrint']==0):
print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors print "stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors
if (self.settings['handCount']!=0): if (self.settings['handCount']!=0):
if ((stored+duplicates+partial+errors)>=self.settings['handCount']): if ((stored+duplicates+partial+errors)>=self.settings['handCount']):
if (not self.settings['quiet']): if (not self.settings['quiet']):
print "quitting due to reaching the amount of hands to be imported" print "quitting due to reaching the amount of hands to be imported"
print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", print "Total stored:", stored, "duplicates:", duplicates, "partial/damaged:", partial, "errors:", errors, " time:", (time() - starttime)
partial, "errors:", errors, " time: %5.3f" % (time() - starttime) sys.exit(0)
sys.exit(0) startpos=endpos
startpos=endpos print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors, " time:", (time() - starttime)
print "Total stored:", stored, "duplicates:", duplicates, "partial:", partial, "errors:", errors, " time: %5.3f" % (time() - starttime)
if stored==0: if stored==0:
if duplicates>0: if duplicates>0:
for line_no in range(len(self.lines)): for line_no in range(len(self.lines)):
if self.lines[line_no].find("Game #")!=-1: if self.lines[line_no].find("Game #")!=-1:
final_game_line=self.lines[line_no] final_game_line=self.lines[line_no]
handsId=fpdb_simple.parseSiteHandNo(final_game_line) handsId=fpdb_simple.parseSiteHandNo(final_game_line)
else: else:
print "failed to read a single hand from file:", inputFile print "failed to read a single hand from file:", inputFile
handsId=0 handsId=0
#todo: this will cause return of an unstored hand number if the last hand was error or partial #todo: this will cause return of an unstored hand number if the last hand was error or partial
self.db.commit() self.db.commit()
self.handsId=handsId self.handsId=handsId
return handsId return handsId
#end def import_file_dict #end def import_file_dict
def parseTourneyHistory(self): def parseTourneyHistory(self):
print "Tourney history parser stub" print "Tourney history parser stub"
#Find tournament boundaries. #Find tournament boundaries.
#print self.foabs #print self.foabs
def printEmailErrorMessage(self, errors, filename, line): def printEmailErrorMessage(self, errors, filename, line):
print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it." traceback.print_exc(file=sys.stderr)
print "Filename:", filename print "Error No.",errors,", please send the hand causing this to steffen@sycamoretest.info so I can fix it."
print "Here is the first line so you can identify it. Please mention that the error was a ValueError:" print "Filename:", filename
print self.hand[0] print "Here is the first line so you can identify it. Please mention that the error was a ValueError:"
print self.hand[0]
print "Hand logged to hand-errors.txt"
logfile = open('hand-errors.txt', 'a')
for s in self.hand:
logfile.write(str(s) + "\n")
logfile.write("\n")
logfile.close()
if __name__ == "__main__": if __name__ == "__main__":
print "CLI for fpdb_import is now available as CliFpdb.py" print "CLI for fpdb_import is now available as CliFpdb.py"

View File

@ -42,6 +42,8 @@ def mainParser(db, cursor, site, category, hand):
smallBlindLine=0 smallBlindLine=0
for i in range(len(hand)): for i in range(len(hand)):
if hand[i].find("posts small blind")!=-1 or hand[i].find("posts the small blind")!=-1: if hand[i].find("posts small blind")!=-1 or hand[i].find("posts the small blind")!=-1:
if hand[i][-2:] == "$0":
continue
smallBlindLine=i smallBlindLine=i
#print "found small blind line:",smallBlindLine #print "found small blind line:",smallBlindLine
break break

3604
pyfpdb/fpdb_simple.py Normal file → Executable file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 48 KiB