Merge branch 'master' of git://git.assembla.com/fpdboz.git
This commit is contained in:
commit
cfdec93d36
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2008, Carl Gherardi
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2010, Matthew Boss
|
||||
#
|
||||
# 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
|
||||
|
@ -18,93 +19,286 @@
|
|||
|
||||
########################################################################
|
||||
|
||||
# Standard Library modules
|
||||
import Configuration
|
||||
import traceback
|
||||
# This code is based heavily on EverleafToFpdb.py, by Carl Gherardi
|
||||
#
|
||||
# OUTSTANDING MATTERS
|
||||
#
|
||||
# -- No siteID assigned
|
||||
# -- No support for games other than NL hold 'em cash. Hand histories for other
|
||||
# games required
|
||||
# -- No support for limit hold 'em yet, though this would be easy to add
|
||||
# -- No support for tournaments (see also the last item below)
|
||||
# -- Assumes that the currency of ring games is USD
|
||||
# -- Only works for 'gametype="2"'. What is 'gametype'?
|
||||
# -- Only accepts 'realmoney="true"'
|
||||
# -- A hand's time-stamp does not record seconds past the minute (a
|
||||
# limitation of the history format)
|
||||
# -- No support for a bring-in or for antes (is the latter in fact unnecessary
|
||||
# for hold 'em on Carbon?)
|
||||
# -- hand.maxseats can only be guessed at
|
||||
# -- The last hand in a history file will often be incomplete and is therefore
|
||||
# rejected
|
||||
# -- Is behaviour currently correct when someone shows an uncalled hand?
|
||||
# -- Information may be lost when the hand ID is converted from the native form
|
||||
# xxxxxxxx-yyy(y*) to xxxxxxxxyyy(y*) (in principle this should be stored as
|
||||
# a string, but the database does not support this). Is there a possibility
|
||||
# of collision between hand IDs that ought to be distinct?
|
||||
# -- Cannot parse tables that run it twice (nor is this likely ever to be
|
||||
# possible)
|
||||
# -- Cannot parse hands in which someone is all in in one of the blinds. Until
|
||||
# this is corrected tournaments will be unparseable
|
||||
|
||||
import sys
|
||||
import re
|
||||
import xml.dom.minidom
|
||||
from xml.dom.minidom import Node
|
||||
from HandHistoryConverter import HandHistoryConverter
|
||||
import logging
|
||||
from HandHistoryConverter import *
|
||||
from decimal import Decimal
|
||||
|
||||
# Carbon format looks like:
|
||||
class Carbon(HandHistoryConverter):
|
||||
|
||||
# 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"/>
|
||||
sitename = "Carbon"
|
||||
filetype = "text"
|
||||
codepage = "cp1252"
|
||||
siteID = 11
|
||||
|
||||
# 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
|
||||
# Static regexes
|
||||
re_SplitHands = re.compile(r'</game>\n+(?=<game)')
|
||||
re_TailSplitHands = re.compile(r'(</game>)')
|
||||
re_GameInfo = re.compile(r'<description type="(?P<GAME>[a-zA-Z ]+)" stakes="(?P<LIMIT>[a-zA-Z ]+) \(\$(?P<SB>[.0-9]+)/\$(?P<BB>[.0-9]+)\)"/>', re.MULTILINE)
|
||||
re_HandInfo = re.compile(r'<game id="(?P<HID1>[0-9]+)-(?P<HID2>[0-9]+)" starttime="(?P<DATETIME>[0-9]+)" numholecards="2" gametype="2" realmoney="true" data="[0-9]+\|(?P<TABLE>[^\(]+)', re.MULTILINE)
|
||||
re_Button = re.compile(r'<players dealer="(?P<BUTTON>[0-9]+)">')
|
||||
re_PlayerInfo = re.compile(r'<player seat="(?P<SEAT>[0-9]+)" nickname="(?P<PNAME>.+)" balance="\$(?P<CASH>[.0-9]+)" dealtin="(?P<DEALTIN>(true|false))" />', re.MULTILINE)
|
||||
re_Board = re.compile(r'<cards type="COMMUNITY" cards="(?P<CARDS>[^"]+)"', re.MULTILINE)
|
||||
re_EndOfHand = re.compile(r'<round id="END_OF_GAME"', re.MULTILINE)
|
||||
|
||||
# The following are also static regexes: there is no need to call
|
||||
# compilePlayerRegexes (which does nothing), since players are identified
|
||||
# not by name but by seat number
|
||||
re_PostSB = re.compile(r'<event sequence="[0-9]+" type="(SMALL_BLIND|RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SB>[.0-9]+)"/>', re.MULTILINE)
|
||||
re_PostBB = re.compile(r'<event sequence="[0-9]+" type="(BIG_BLIND|INITIAL_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<BB>[.0-9]+)"/>', re.MULTILINE)
|
||||
re_PostBoth = re.compile(r'<event sequence="[0-9]+" type="(RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SBBB>[.0-9]+)"/>', re.MULTILINE)
|
||||
#re_Antes = ???
|
||||
#re_BringIn = ???
|
||||
re_HeroCards = re.compile(r'<cards type="HOLE" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"', re.MULTILINE)
|
||||
re_Action = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT)" player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?/>', re.MULTILINE)
|
||||
re_ShowdownAction = re.compile(r'<cards type="SHOWN" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE)
|
||||
re_CollectPot = re.compile(r'<winner amount="(?P<POT>[.0-9]+)" uncalled="(true|false)" potnumber="[0-9]+" player="(?P<PSEAT>[0-9])"', re.MULTILINE)
|
||||
re_SitsOut = re.compile(r'<event sequence="[0-9]+" type="SIT_OUT" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE)
|
||||
re_ShownCards = re.compile(r'<cards type="(SHOWN|MUCKED)" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE)
|
||||
|
||||
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")
|
||||
self.siteId = 4 # Needs to match id entry in Sites database
|
||||
def compilePlayerRegexs(self, hand):
|
||||
pass
|
||||
|
||||
def playerNameFromSeatNo(self, seatNo, hand):
|
||||
# This special function is required because Carbon Poker records
|
||||
# actions by seat number, not by the player's name
|
||||
for p in hand.players:
|
||||
if p[0] == int(seatNo):
|
||||
return p[1]
|
||||
|
||||
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"]
|
||||
return [["ring", "hold", "nl"],
|
||||
["tour", "hold", "nl"]]
|
||||
|
||||
def determineGameType(self, handText):
|
||||
"""return dict with keys/values:
|
||||
'type' in ('ring', 'tour')
|
||||
'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl')
|
||||
'base' in ('hold', 'stud', 'draw')
|
||||
'category' in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi')
|
||||
'hilo' in ('h','l','s')
|
||||
'smallBlind' int?
|
||||
'bigBlind' int?
|
||||
'smallBet'
|
||||
'bigBet'
|
||||
'currency' in ('USD', 'EUR', 'T$', <countrycode>)
|
||||
or None if we fail to get the info """
|
||||
|
||||
m = self.re_GameInfo.search(handText)
|
||||
if not m:
|
||||
# Information about the game type appears only at the beginning of
|
||||
# a hand history file; hence it is not supplied with the second
|
||||
# and subsequent hands. In these cases we use the value previously
|
||||
# stored.
|
||||
return self.info
|
||||
self.info = {}
|
||||
mg = m.groupdict()
|
||||
|
||||
limits = { 'No Limit':'nl', 'Limit':'fl' }
|
||||
games = { # base, category
|
||||
'Holdem' : ('hold','holdem'),
|
||||
'Holdem Tournament' : ('hold','holdem') }
|
||||
|
||||
if 'LIMIT' in mg:
|
||||
self.info['limitType'] = limits[mg['LIMIT']]
|
||||
if 'GAME' in mg:
|
||||
(self.info['base'], self.info['category']) = games[mg['GAME']]
|
||||
if 'SB' in mg:
|
||||
self.info['sb'] = mg['SB']
|
||||
if 'BB' in mg:
|
||||
self.info['bb'] = mg['BB']
|
||||
if mg['GAME'] == 'Holdem Tournament':
|
||||
self.info['type'] = 'tour'
|
||||
self.info['currency'] = 'T$'
|
||||
else:
|
||||
print "Carbon: Unknown gametype: '%s'" % (type)
|
||||
self.info['type'] = 'ring'
|
||||
self.info['currency'] = 'USD'
|
||||
|
||||
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)
|
||||
return self.info
|
||||
|
||||
if(m.group('LIMIT') == "No Limit"):
|
||||
gametype = gametype + ["nl"]
|
||||
def readHandInfo(self, hand):
|
||||
m = self.re_HandInfo.search(hand.handText)
|
||||
if m is None:
|
||||
logging.info("Didn't match re_HandInfo")
|
||||
logging.info(hand.handText)
|
||||
return None
|
||||
logging.debug("HID %s-%s, Table %s" % (m.group('HID1'),
|
||||
m.group('HID2'), m.group('TABLE')[:-1]))
|
||||
hand.handid = m.group('HID1') + m.group('HID2')
|
||||
hand.tablename = m.group('TABLE')[:-1]
|
||||
hand.maxseats = 2 # This value may be increased as necessary
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME')[:12],
|
||||
'%Y%m%d%H%M')
|
||||
# Check that the hand is complete up to the awarding of the pot; if
|
||||
# not, the hand is unparseable
|
||||
if self.re_EndOfHand.search(hand.handText) is None:
|
||||
raise FpdbParseError(hid=m.group('HID1') + "-" + m.group('HID2'))
|
||||
|
||||
gametype = gametype + [self.float2int(m.group('SB'))]
|
||||
gametype = gametype + [self.float2int(m.group('BB'))]
|
||||
def readPlayerStacks(self, hand):
|
||||
m = self.re_PlayerInfo.finditer(hand.handText)
|
||||
for a in m:
|
||||
seatno = int(a.group('SEAT'))
|
||||
# It may be necessary to adjust 'hand.maxseats', which is an
|
||||
# educated guess, starting with 2 (indicating a heads-up table) and
|
||||
# adjusted upwards in steps to 6, then 9, then 10. An adjustment is
|
||||
# made whenever a player is discovered whose seat number is
|
||||
# currently above the maximum allowable for the table.
|
||||
if seatno >= hand.maxseats:
|
||||
if seatno > 8:
|
||||
hand.maxseats = 10
|
||||
elif seatno > 5:
|
||||
hand.maxseats = 9
|
||||
else:
|
||||
hand.maxseats = 6
|
||||
if a.group('DEALTIN') == "true":
|
||||
hand.addPlayer(seatno, a.group('PNAME'), a.group('CASH'))
|
||||
|
||||
return gametype
|
||||
def markStreets(self, hand):
|
||||
#if hand.gametype['base'] == 'hold':
|
||||
m = re.search(r'<round id="PREFLOP" sequence="[0-9]+">(?P<PREFLOP>.+(?=<round id="POSTFLOP")|.+)(<round id="POSTFLOP" sequence="[0-9]+">(?P<FLOP>.+(?=<round id="POSTTURN")|.+))?(<round id="POSTTURN" sequence="[0-9]+">(?P<TURN>.+(?=<round id="POSTRIVER")|.+))?(<round id="POSTRIVER" sequence="[0-9]+">(?P<RIVER>.+))?', hand.handText, re.DOTALL)
|
||||
hand.addStreets(m)
|
||||
|
||||
def readPlayerStacks(self):
|
||||
pass
|
||||
def readBlinds(self):
|
||||
pass
|
||||
def readAction(self):
|
||||
pass
|
||||
def readCommunityCards(self, hand, street):
|
||||
m = self.re_Board.search(hand.streets[street])
|
||||
if street == 'FLOP':
|
||||
hand.setCommunityCards(street, m.group('CARDS').split(','))
|
||||
elif street in ('TURN','RIVER'):
|
||||
hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]])
|
||||
|
||||
# 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>"
|
||||
def readAntes(self, hand):
|
||||
pass # ???
|
||||
|
||||
def readBringIn(self, hand):
|
||||
pass # ???
|
||||
|
||||
def readBlinds(self, hand):
|
||||
try:
|
||||
doc = xml.dom.minidom.parseString(self.obs)
|
||||
self.doc = doc
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
m = self.re_PostSB.search(hand.handText)
|
||||
hand.addBlind(self.playerNameFromSeatNo(m.group('PSEAT'), hand),
|
||||
'small blind', m.group('SB'))
|
||||
except: # no small blind
|
||||
hand.addBlind(None, None, None)
|
||||
for a in self.re_PostBB.finditer(hand.handText):
|
||||
hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), hand),
|
||||
'big blind', a.group('BB'))
|
||||
for a in self.re_PostBoth.finditer(hand.handText):
|
||||
bb = Decimal(self.info['bb'])
|
||||
amount = Decimal(a.group('SBBB'))
|
||||
if amount < bb:
|
||||
hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'),
|
||||
hand), 'small blind', a.group('SBBB'))
|
||||
elif amount == bb:
|
||||
hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'),
|
||||
hand), 'big blind', a.group('SBBB'))
|
||||
else:
|
||||
hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'),
|
||||
hand), 'both', a.group('SBBB'))
|
||||
|
||||
def readButton(self, hand):
|
||||
hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
|
||||
|
||||
def readHeroCards(self, hand):
|
||||
m = self.re_HeroCards.search(hand.handText)
|
||||
if m:
|
||||
hand.hero = self.playerNameFromSeatNo(m.group('PSEAT'), hand)
|
||||
cards = m.group('CARDS').split(',')
|
||||
hand.addHoleCards('PREFLOP', hand.hero, closed=cards, shown=False,
|
||||
mucked=False, dealt=True)
|
||||
|
||||
def readAction(self, hand, street):
|
||||
logging.debug("readAction (%s)" % street)
|
||||
m = self.re_Action.finditer(hand.streets[street])
|
||||
for action in m:
|
||||
logging.debug("%s %s" % (action.group('ATYPE'),
|
||||
action.groupdict()))
|
||||
player = self.playerNameFromSeatNo(action.group('PSEAT'), hand)
|
||||
if action.group('ATYPE') == 'RAISE':
|
||||
hand.addCallandRaise(street, player, action.group('BET'))
|
||||
elif action.group('ATYPE') == 'CALL':
|
||||
hand.addCall(street, player, action.group('BET'))
|
||||
elif action.group('ATYPE') == 'BET':
|
||||
hand.addBet(street, player, action.group('BET'))
|
||||
elif action.group('ATYPE') in ('FOLD', 'SIT_OUT'):
|
||||
hand.addFold(street, player)
|
||||
elif action.group('ATYPE') == 'CHECK':
|
||||
hand.addCheck(street, player)
|
||||
elif action.group('ATYPE') == 'ALL_IN':
|
||||
hand.addAllIn(street, player, action.group('BET'))
|
||||
else:
|
||||
logging.debug("Unimplemented readAction: %s %s"
|
||||
% (action.group('PSEAT'),action.group('ATYPE'),))
|
||||
|
||||
def readShowdownActions(self, hand):
|
||||
for shows in self.re_ShowdownAction.finditer(hand.handText):
|
||||
cards = shows.group('CARDS').split(',')
|
||||
hand.addShownCards(cards,
|
||||
self.playerNameFromSeatNo(shows.group('PSEAT'),
|
||||
hand))
|
||||
|
||||
def readCollectPot(self, hand):
|
||||
pots = [Decimal(0) for n in range(hand.maxseats)]
|
||||
for m in self.re_CollectPot.finditer(hand.handText):
|
||||
pots[int(m.group('PSEAT'))] += Decimal(m.group('POT'))
|
||||
# Regarding the processing logic for "committed", see Pot.end() in
|
||||
# Hand.py
|
||||
committed = sorted([(v,k) for (k,v) in hand.pot.committed.items()])
|
||||
for p in range(hand.maxseats):
|
||||
pname = self.playerNameFromSeatNo(p, hand)
|
||||
if committed[-1][1] == pname:
|
||||
pots[p] -= committed[-1][0] - committed[-2][0]
|
||||
if pots[p] > 0:
|
||||
hand.addCollectPot(player=pname, pot=pots[p])
|
||||
|
||||
def readShownCards(self, hand):
|
||||
for m in self.re_ShownCards.finditer(hand.handText):
|
||||
cards = m.group('CARDS').split(',')
|
||||
hand.addShownCards(cards=cards, player=self.playerNameFromSeatNo(m.group('PSEAT'), hand))
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Configuration.Config()
|
||||
e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml")
|
||||
e.processFile()
|
||||
print str(e)
|
||||
parser = OptionParser()
|
||||
parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-")
|
||||
parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
|
||||
parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
|
||||
parser.add_option("-q", "--quiet", action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
|
||||
parser.add_option("-v", "--verbose", action="store_const", const=logging.INFO, dest="verbosity")
|
||||
parser.add_option("--vv", action="store_const", const=logging.DEBUG, dest="verbosity")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
LOG_FILENAME = './logging.out'
|
||||
logging.basicConfig(filename=LOG_FILENAME, level=options.verbosity)
|
||||
|
||||
e = Carbon(in_path = options.ipath,
|
||||
out_path = options.opath,
|
||||
follow = options.follow,
|
||||
autostart = True)
|
||||
|
||||
|
|
|
@ -56,33 +56,33 @@ def get_exec_path():
|
|||
if hasattr(sys, "frozen"): # compiled by py2exe
|
||||
return os.path.dirname(sys.executable)
|
||||
else:
|
||||
pathname = os.path.dirname(sys.argv[0])
|
||||
return os.path.abspath(pathname)
|
||||
return sys.path[0]
|
||||
|
||||
def get_config(file_name, fallback = True):
|
||||
"""Looks in cwd and in self.default_config_path for a config file."""
|
||||
config_path = os.path.join(get_exec_path(), file_name)
|
||||
# print "config_path=", config_path
|
||||
if os.path.exists(config_path): # there is a file in the cwd
|
||||
return config_path # so we use it
|
||||
else: # no file in the cwd, look where it should be in the first place
|
||||
config_path = os.path.join(get_default_config_path(), file_name)
|
||||
# print "config path 2=", config_path
|
||||
if os.path.exists(config_path):
|
||||
"""Looks in exec dir and in self.default_config_path for a config file."""
|
||||
config_path = os.path.join(DIR_SELF, file_name) # look in exec dir
|
||||
if os.path.exists(config_path) and os.path.isfile(config_path):
|
||||
return config_path # there is a file in the exec dir so we use it
|
||||
else:
|
||||
config_path = os.path.join(DIR_CONFIG, file_name) # look in config dir
|
||||
if os.path.exists(config_path) and os.path.isfile(config_path):
|
||||
return config_path
|
||||
|
||||
# No file found
|
||||
if not fallback:
|
||||
return False
|
||||
|
||||
# OK, fall back to the .example file, should be in the start dir
|
||||
if os.path.exists(file_name + ".example"):
|
||||
# OK, fall back to the .example file, should be in the exec dir
|
||||
if os.path.exists(os.path.join(DIR_SELF, file_name + ".example")):
|
||||
try:
|
||||
shutil.copyfile(file_name + ".example", file_name)
|
||||
shutil.copyfile(os.path.join(DIR_SELF, file_name + ".example"), os.path.join(DIR_CONFIG, file_name))
|
||||
print "No %s found, using %s.example.\n" % (file_name, file_name)
|
||||
print "A %s file has been created. You will probably have to edit it." % file_name
|
||||
sys.stderr.write("No %s found, using %s.example.\n" % (file_name, file_name) )
|
||||
print "A %s file has been created. You will probably have to edit it." % os.path.join(DIR_CONFIG, file_name)
|
||||
log.error("No %s found, using %s.example.\n" % (file_name, file_name) )
|
||||
except:
|
||||
print "No %s found, cannot fall back. Exiting.\n" % file_name
|
||||
sys.exit()
|
||||
else:
|
||||
print "No %s found, cannot fall back. Exiting.\n" % file_name
|
||||
sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name)
|
||||
sys.exit()
|
||||
|
@ -94,18 +94,26 @@ def get_logger(file_name, config = "config", fallback = False):
|
|||
try:
|
||||
logging.config.fileConfig(conf)
|
||||
log = logging.getLogger(config)
|
||||
log.debug("%s logger initialised" % config)
|
||||
return log
|
||||
except:
|
||||
pass
|
||||
|
||||
log = logging.basicConfig()
|
||||
log = logging.getLogger()
|
||||
log.debug("config logger initialised")
|
||||
log.error("basicConfig logger initialised")
|
||||
return log
|
||||
|
||||
# find a logging.conf file and set up logging
|
||||
log = get_logger("logging.conf")
|
||||
def check_dir(path, create = True):
|
||||
"""Check if a dir exists, optionally creates if not."""
|
||||
if os.path.exists(path):
|
||||
if os.path.isdir(path):
|
||||
return path
|
||||
else:
|
||||
return False
|
||||
if create:
|
||||
print "creating directory %s" % path
|
||||
else:
|
||||
return False
|
||||
|
||||
########################################################################
|
||||
# application wide consts
|
||||
|
@ -113,19 +121,31 @@ log = get_logger("logging.conf")
|
|||
APPLICATION_NAME_SHORT = 'fpdb'
|
||||
APPLICATION_VERSION = 'xx.xx.xx'
|
||||
|
||||
DIR_SELF = os.path.dirname(get_exec_path())
|
||||
#TODO: imo no good idea to place 'database' in parent dir
|
||||
DIR_DATABASES = os.path.join(os.path.dirname(DIR_SELF), 'database')
|
||||
DIR_SELF = get_exec_path()
|
||||
DIR_CONFIG = check_dir(get_default_config_path())
|
||||
DIR_DATABASE = check_dir(os.path.join(DIR_CONFIG, 'database'))
|
||||
DIR_LOG = check_dir(os.path.join(DIR_CONFIG, 'log'))
|
||||
|
||||
DATABASE_TYPE_POSTGRESQL = 'postgresql'
|
||||
DATABASE_TYPE_SQLITE = 'sqlite'
|
||||
DATABASE_TYPE_MYSQL = 'mysql'
|
||||
#TODO: should this be a tuple or a dict
|
||||
DATABASE_TYPES = (
|
||||
DATABASE_TYPE_POSTGRESQL,
|
||||
DATABASE_TYPE_SQLITE,
|
||||
DATABASE_TYPE_MYSQL,
|
||||
)
|
||||
|
||||
# find a logging.conf file and set up logging
|
||||
log = get_logger("logging.conf", config = "config")
|
||||
log.debug("config logger initialised")
|
||||
|
||||
# and then log our consts
|
||||
log.info("DIR SELF = %s" % DIR_SELF)
|
||||
log.info("DIR CONFIG = %s" % DIR_CONFIG)
|
||||
log.info("DIR DATABASE = %s" % DIR_DATABASE)
|
||||
log.info("DIR LOG = %s" % DIR_LOG)
|
||||
NEWIMPORT = True
|
||||
LOCALE_ENCODING = locale.getdefaultlocale()[1]
|
||||
|
||||
########################################################################
|
||||
|
@ -408,11 +428,10 @@ class Config:
|
|||
if file is not None: # config file path passed in
|
||||
file = os.path.expanduser(file)
|
||||
if not os.path.exists(file):
|
||||
print "Configuration file %s not found. Using defaults." % (file)
|
||||
sys.stderr.write("Configuration file %s not found. Using defaults." % (file))
|
||||
log.error("Specified configuration file %s not found. Using defaults." % (file))
|
||||
file = None
|
||||
|
||||
if file is None: file = get_config("HUD_config.xml")
|
||||
if file is None: file = get_config("HUD_config.xml", True)
|
||||
|
||||
# Parse even if there was no real config file found and we are using the example
|
||||
# If using the example, we'll edit it later
|
||||
|
@ -429,6 +448,8 @@ class Config:
|
|||
|
||||
self.doc = doc
|
||||
self.file = file
|
||||
self.dir = os.path.dirname(self.file)
|
||||
self.dir_databases = os.path.join(self.dir, 'database')
|
||||
self.supported_sites = {}
|
||||
self.supported_games = {}
|
||||
self.supported_databases = {} # databaseName --> Database instance
|
||||
|
|
|
@ -38,11 +38,31 @@ from decimal import Decimal
|
|||
import string
|
||||
import re
|
||||
import Queue
|
||||
import codecs
|
||||
import logging
|
||||
import math
|
||||
|
||||
|
||||
# pyGTK modules
|
||||
|
||||
|
||||
# Other library modules
|
||||
try:
|
||||
import sqlalchemy.pool as pool
|
||||
use_pool = True
|
||||
except ImportError:
|
||||
logging.info("Not using sqlalchemy connection pool.")
|
||||
use_pool = False
|
||||
|
||||
try:
|
||||
from numpy import var
|
||||
use_numpy = True
|
||||
except ImportError:
|
||||
logging.info("Not using numpy to define variance in sqlite.")
|
||||
use_numpy = False
|
||||
|
||||
|
||||
# FreePokerTools modules
|
||||
import fpdb_db
|
||||
import Configuration
|
||||
import SQL
|
||||
import Card
|
||||
|
@ -50,7 +70,29 @@ import Tourney
|
|||
import Charset
|
||||
from Exceptions import *
|
||||
|
||||
log = Configuration.get_logger("logging.conf")
|
||||
log = Configuration.get_logger("logging.conf", config = "db")
|
||||
log.debug("db logger initialized.")
|
||||
encoder = codecs.lookup('utf-8')
|
||||
|
||||
DB_VERSION = 119
|
||||
|
||||
|
||||
# Variance created as sqlite has a bunch of undefined aggregate functions.
|
||||
|
||||
class VARIANCE:
|
||||
def __init__(self):
|
||||
self.store = []
|
||||
|
||||
def step(self, value):
|
||||
self.store.append(value)
|
||||
|
||||
def finalize(self):
|
||||
return float(var(self.store))
|
||||
|
||||
class sqlitemath:
|
||||
def mod(self, a, b):
|
||||
return a%b
|
||||
|
||||
|
||||
class Database:
|
||||
|
||||
|
@ -188,15 +230,14 @@ class Database:
|
|||
log.info("Creating Database instance, sql = %s" % sql)
|
||||
self.config = c
|
||||
self.__connected = False
|
||||
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
|
||||
self.do_connect(c)
|
||||
|
||||
if self.backend == self.PGSQL:
|
||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE
|
||||
#ISOLATION_LEVEL_AUTOCOMMIT = 0
|
||||
#ISOLATION_LEVEL_READ_COMMITTED = 1
|
||||
#ISOLATION_LEVEL_SERIALIZABLE = 2
|
||||
|
||||
self.settings = {}
|
||||
self.settings['os'] = "linuxmac" if os.name != "nt" else "windows"
|
||||
db_params = c.get_db_parameters()
|
||||
self.import_options = c.get_import_parameters()
|
||||
self.backend = db_params['db-backend']
|
||||
self.db_server = db_params['db-server']
|
||||
self.database = db_params['db-databaseName']
|
||||
self.host = db_params['db-host']
|
||||
|
||||
# where possible avoid creating new SQL instance by using the global one passed in
|
||||
if sql is None:
|
||||
|
@ -204,6 +245,15 @@ class Database:
|
|||
else:
|
||||
self.sql = sql
|
||||
|
||||
# connect to db
|
||||
self.do_connect(c)
|
||||
print "connection =", self.connection
|
||||
if self.backend == self.PGSQL:
|
||||
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE
|
||||
#ISOLATION_LEVEL_AUTOCOMMIT = 0
|
||||
#ISOLATION_LEVEL_READ_COMMITTED = 1
|
||||
#ISOLATION_LEVEL_SERIALIZABLE = 2
|
||||
|
||||
if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion:
|
||||
log.info("sqlite/:memory: - creating")
|
||||
self.recreate_tables()
|
||||
|
@ -226,8 +276,6 @@ class Database:
|
|||
self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero
|
||||
self.date_nhands_ago = {} # dates N hands ago per player - not used yet
|
||||
|
||||
self.cursor = self.fdb.cursor
|
||||
|
||||
self.saveActions = False if self.import_options['saveActions'] == False else True
|
||||
|
||||
self.connection.rollback() # make sure any locks taken so far are released
|
||||
|
@ -238,14 +286,20 @@ class Database:
|
|||
self.hud_style = style
|
||||
|
||||
def do_connect(self, c):
|
||||
if c is None:
|
||||
raise FpdbError('Configuration not defined')
|
||||
|
||||
db = c.get_db_parameters()
|
||||
try:
|
||||
self.fdb.do_connect(c)
|
||||
self.connect(backend=db['db-backend'],
|
||||
host=db['db-host'],
|
||||
database=db['db-databaseName'],
|
||||
user=db['db-user'],
|
||||
password=db['db-password'])
|
||||
except:
|
||||
# error during connect
|
||||
self.__connected = False
|
||||
raise
|
||||
self.connection = self.fdb.db
|
||||
self.wrongDbVersion = self.fdb.wrongDbVersion
|
||||
|
||||
db_params = c.get_db_parameters()
|
||||
self.import_options = c.get_import_parameters()
|
||||
|
@ -255,11 +309,137 @@ class Database:
|
|||
self.host = db_params['db-host']
|
||||
self.__connected = True
|
||||
|
||||
def connect(self, backend=None, host=None, database=None,
|
||||
user=None, password=None):
|
||||
"""Connects a database with the given parameters"""
|
||||
if backend is None:
|
||||
raise FpdbError('Database backend not defined')
|
||||
self.backend = backend
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.database = database
|
||||
self.connection = None
|
||||
self.cursor = None
|
||||
|
||||
if backend == Database.MYSQL_INNODB:
|
||||
import MySQLdb
|
||||
if use_pool:
|
||||
MySQLdb = pool.manage(MySQLdb, pool_size=5)
|
||||
try:
|
||||
self.connection = MySQLdb.connect(host=host, user=user, passwd=password, db=database, use_unicode=True)
|
||||
#TODO: Add port option
|
||||
except MySQLdb.Error, ex:
|
||||
if ex.args[0] == 1045:
|
||||
raise FpdbMySQLAccessDenied(ex.args[0], ex.args[1])
|
||||
elif ex.args[0] == 2002 or ex.args[0] == 2003: # 2002 is no unix socket, 2003 is no tcp socket
|
||||
raise FpdbMySQLNoDatabase(ex.args[0], ex.args[1])
|
||||
else:
|
||||
print "*** WARNING UNKNOWN MYSQL ERROR", ex
|
||||
elif backend == Database.PGSQL:
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
if use_pool:
|
||||
psycopg2 = pool.manage(psycopg2, pool_size=5)
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
# If DB connection is made over TCP, then the variables
|
||||
# host, user and password are required
|
||||
# For local domain-socket connections, only DB name is
|
||||
# needed, and everything else is in fact undefined and/or
|
||||
# flat out wrong
|
||||
# sqlcoder: This database only connect failed in my windows setup??
|
||||
# Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere?
|
||||
connected = False
|
||||
if self.host == "localhost" or self.host == "127.0.0.1":
|
||||
try:
|
||||
self.connection = psycopg2.connect(database = database)
|
||||
connected = True
|
||||
except:
|
||||
# direct connection failed so try user/pass/... version
|
||||
pass
|
||||
if not connected:
|
||||
try:
|
||||
self.connection = psycopg2.connect(host = host,
|
||||
user = user,
|
||||
password = password,
|
||||
database = database)
|
||||
except Exception, ex:
|
||||
if 'Connection refused' in ex.args[0]:
|
||||
# meaning eg. db not running
|
||||
raise FpdbPostgresqlNoDatabase(errmsg = ex.args[0])
|
||||
elif 'password authentication' in ex.args[0]:
|
||||
raise FpdbPostgresqlAccessDenied(errmsg = ex.args[0])
|
||||
else:
|
||||
msg = ex.args[0]
|
||||
print msg
|
||||
raise FpdbError(msg)
|
||||
elif backend == Database.SQLITE:
|
||||
logging.info("Connecting to SQLite: %(database)s" % {'database':database})
|
||||
import sqlite3
|
||||
if use_pool:
|
||||
sqlite3 = pool.manage(sqlite3, pool_size=1)
|
||||
else:
|
||||
logging.warning("SQLite won't work well without 'sqlalchemy' installed.")
|
||||
|
||||
if database != ":memory:":
|
||||
if not os.path.isdir(self.config.dir_databases):
|
||||
print "Creating directory: '%s'" % (self.config.dir_databases)
|
||||
logging.info("Creating directory: '%s'" % (self.config.dir_databases))
|
||||
os.mkdir(self.config.dir_databases)
|
||||
database = os.path.join(self.config.dir_databases, database)
|
||||
logging.info(" sqlite db: " + database)
|
||||
self.connection = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES )
|
||||
sqlite3.register_converter("bool", lambda x: bool(int(x)))
|
||||
sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
|
||||
self.connection.create_function("floor", 1, math.floor)
|
||||
tmp = sqlitemath()
|
||||
self.connection.create_function("mod", 2, tmp.mod)
|
||||
if use_numpy:
|
||||
self.connection.create_aggregate("variance", 1, VARIANCE)
|
||||
else:
|
||||
logging.warning("Some database functions will not work without NumPy support")
|
||||
else:
|
||||
raise FpdbError("unrecognised database backend:"+backend)
|
||||
|
||||
self.cursor = self.connection.cursor()
|
||||
self.cursor.execute(self.sql.query['set tx level'])
|
||||
self.check_version(database=database, create=True)
|
||||
|
||||
|
||||
def check_version(self, database, create):
|
||||
self.wrongDbVersion = False
|
||||
try:
|
||||
self.cursor.execute("SELECT * FROM Settings")
|
||||
settings = self.cursor.fetchone()
|
||||
if settings[0] != DB_VERSION:
|
||||
logging.error("outdated or too new database version (%s) - please recreate tables"
|
||||
% (settings[0]))
|
||||
self.wrongDbVersion = True
|
||||
except:# _mysql_exceptions.ProgrammingError:
|
||||
if database != ":memory:":
|
||||
if create:
|
||||
print "Failed to read settings table - recreating tables"
|
||||
log.info("failed to read settings table - recreating tables")
|
||||
self.recreate_tables()
|
||||
self.check_version(database=database, create=False)
|
||||
if not self.wrongDbVersion:
|
||||
msg = "Edit your screen_name and hand history path in the supported_sites "\
|
||||
+"section of the \nPreferences window (Main menu) before trying to import hands"
|
||||
print "\n%s" % msg
|
||||
log.warning(msg)
|
||||
else:
|
||||
print "Failed to read settings table - please recreate tables"
|
||||
log.info("failed to read settings table - please recreate tables")
|
||||
self.wrongDbVersion = True
|
||||
else:
|
||||
self.wrongDbVersion = True
|
||||
#end def connect
|
||||
|
||||
def commit(self):
|
||||
self.fdb.db.commit()
|
||||
self.connection.commit()
|
||||
|
||||
def rollback(self):
|
||||
self.fdb.db.rollback()
|
||||
self.connection.rollback()
|
||||
|
||||
def connected(self):
|
||||
return self.__connected
|
||||
|
@ -272,11 +452,18 @@ class Database:
|
|||
|
||||
def disconnect(self, due_to_error=False):
|
||||
"""Disconnects the DB (rolls back if param is true, otherwise commits"""
|
||||
self.fdb.disconnect(due_to_error)
|
||||
if due_to_error:
|
||||
self.connection.rollback()
|
||||
else:
|
||||
self.connection.commit()
|
||||
self.cursor.close()
|
||||
self.connection.close()
|
||||
|
||||
def reconnect(self, due_to_error=False):
|
||||
"""Reconnects the DB"""
|
||||
self.fdb.reconnect(due_to_error=False)
|
||||
#print "started reconnect"
|
||||
self.disconnect(due_to_error)
|
||||
self.connect(self.backend, self.host, self.database, self.user, self.password)
|
||||
|
||||
def get_backend_name(self):
|
||||
"""Returns the name of the currently used backend"""
|
||||
|
@ -289,6 +476,9 @@ class Database:
|
|||
else:
|
||||
raise FpdbError("invalid backend")
|
||||
|
||||
def get_db_info(self):
|
||||
return (self.host, self.database, self.user, self.password)
|
||||
|
||||
def get_table_name(self, hand_id):
|
||||
c = self.connection.cursor()
|
||||
c.execute(self.sql.query['get_table_name'], (hand_id, ))
|
||||
|
@ -845,6 +1035,7 @@ class Database:
|
|||
self.create_tables()
|
||||
self.createAllIndexes()
|
||||
self.commit()
|
||||
print "Finished recreating tables"
|
||||
log.info("Finished recreating tables")
|
||||
#end def recreate_tables
|
||||
|
||||
|
@ -1110,7 +1301,7 @@ class Database:
|
|||
|
||||
def fillDefaultData(self):
|
||||
c = self.get_cursor()
|
||||
c.execute("INSERT INTO Settings (version) VALUES (118);")
|
||||
c.execute("INSERT INTO Settings (version) VALUES (%s);" % (DB_VERSION))
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')")
|
||||
|
@ -1121,6 +1312,7 @@ class Database:
|
|||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Partouche', 'EUR')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')")
|
||||
if self.backend == self.SQLITE:
|
||||
c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
|
||||
elif self.backend == self.PGSQL:
|
||||
|
@ -1266,7 +1458,7 @@ class Database:
|
|||
try:
|
||||
self.get_cursor().execute(self.sql.query['lockForInsert'])
|
||||
except:
|
||||
print "Error during fdb.lock_for_insert:", str(sys.exc_value)
|
||||
print "Error during lock_for_insert:", str(sys.exc_value)
|
||||
#end def lock_for_insert
|
||||
|
||||
###########################
|
||||
|
@ -1285,6 +1477,7 @@ class Database:
|
|||
p['tableName'],
|
||||
p['gameTypeId'],
|
||||
p['siteHandNo'],
|
||||
0, # tourneyId: 0 means not a tourney hand
|
||||
p['handStart'],
|
||||
datetime.today(), #importtime
|
||||
p['seats'],
|
||||
|
|
|
@ -27,8 +27,8 @@ import gobject
|
|||
#import pokereval
|
||||
|
||||
import Configuration
|
||||
import fpdb_db
|
||||
import FpdbSQLQueries
|
||||
import Database
|
||||
import SQL
|
||||
import Charset
|
||||
|
||||
class Filters(threading.Thread):
|
||||
|
@ -800,10 +800,10 @@ def main(argv=None):
|
|||
config = Configuration.Config()
|
||||
db = None
|
||||
|
||||
db = fpdb_db.fpdb_db()
|
||||
db = Database.Database()
|
||||
db.do_connect(config)
|
||||
|
||||
qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name())
|
||||
qdict = SQL.SQL(db.get_backend_name())
|
||||
|
||||
i = Filters(db, config, qdict)
|
||||
main_window = gtk.Window()
|
||||
|
|
|
@ -18,12 +18,10 @@
|
|||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
########################################################################
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from HandHistoryConverter import *
|
||||
|
||||
# Fulltilt HH Format converter
|
||||
# TODO: cat tourno and table to make table name for tournaments
|
||||
|
||||
class Fulltilt(HandHistoryConverter):
|
||||
|
||||
|
@ -67,8 +65,8 @@ class Fulltilt(HandHistoryConverter):
|
|||
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
|
||||
''', re.VERBOSE)
|
||||
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
|
||||
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
|
||||
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
|
||||
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
|
||||
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
|
||||
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
|
||||
|
||||
#static regex for tourney purpose
|
||||
|
@ -191,7 +189,6 @@ class Fulltilt(HandHistoryConverter):
|
|||
if mg['TOURNO'] is None: info['type'] = "ring"
|
||||
else: info['type'] = "tour"
|
||||
# NB: SB, BB must be interpreted as blinds or bets depending on limit type.
|
||||
# if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting
|
||||
return info
|
||||
|
||||
def readHandInfo(self, hand):
|
||||
|
@ -258,15 +255,16 @@ class Fulltilt(HandHistoryConverter):
|
|||
#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')))
|
||||
#FIXME: hand.buttonpos = int(m.group('BUTTON'))
|
||||
|
||||
def readPlayerStacks(self, hand):
|
||||
# Split hand text for FTP, as the regex matches the player names incorrectly
|
||||
# in the summary section
|
||||
pre, post = hand.handText.split('SUMMARY')
|
||||
if hand.gametype['type'] == "ring" :
|
||||
m = self.re_PlayerInfo.finditer(hand.handText)
|
||||
m = self.re_PlayerInfo.finditer(pre)
|
||||
else: #if hand.gametype['type'] == "tour"
|
||||
m = self.re_TourneyPlayerInfo.finditer(hand.handText)
|
||||
m = self.re_TourneyPlayerInfo.finditer(pre)
|
||||
|
||||
players = []
|
||||
for a in m:
|
||||
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
|
||||
|
||||
|
@ -422,7 +420,6 @@ class Fulltilt(HandHistoryConverter):
|
|||
hand.mixed = self.mixes[m.groupdict()['MIXED']]
|
||||
|
||||
def readSummaryInfo(self, summaryInfoList):
|
||||
starttime = time.time()
|
||||
self.status = True
|
||||
|
||||
m = re.search("Tournament Summary", summaryInfoList[0])
|
||||
|
@ -542,14 +539,14 @@ class Fulltilt(HandHistoryConverter):
|
|||
tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
|
||||
else :
|
||||
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin:
|
||||
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (touney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
|
||||
log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (tourney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) )
|
||||
tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
|
||||
if mg['FEE'] is not None:
|
||||
if tourney.fee is None:
|
||||
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
|
||||
else :
|
||||
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee:
|
||||
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (touney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
|
||||
log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (tourney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) )
|
||||
tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
|
||||
|
||||
if tourney.buyin is None:
|
||||
|
|
|
@ -300,7 +300,6 @@ if __name__== "__main__":
|
|||
(options, argv) = parser.parse_args()
|
||||
|
||||
config = Configuration.Config()
|
||||
# db = fpdb_db.fpdb_db()
|
||||
|
||||
settings = {}
|
||||
settings['minPrint'] = options.minPrint
|
||||
|
|
|
@ -27,7 +27,6 @@ from time import time, strftime
|
|||
import Card
|
||||
import fpdb_import
|
||||
import Database
|
||||
import fpdb_db
|
||||
import Filters
|
||||
import Charset
|
||||
|
||||
|
|
|
@ -445,6 +445,43 @@ Left-Drag to Move"
|
|||
<location seat="9" x="70" y="53"> </location>
|
||||
</layout>
|
||||
</site>
|
||||
|
||||
|
||||
<site HH_path="C:/Program Files/Carbon Poker/HandHistory/YOUR SCREEN NAME HERE/" converter="CarbonToFpdb" decoder="everleaf_decode_table" enabled="True" screen_name="YOUR SCREEN NAME HERE" site_name="Carbon" site_path="C:/Program Files/Carbin/" supported_games="holdem" table_finder="Carbon Poker.exe">
|
||||
<layout fav_seat="0" height="547" max="8" width="794">
|
||||
<location seat="1" x="640" y="64"> </location>
|
||||
<location seat="2" x="650" y="230"> </location>
|
||||
<location seat="3" x="650" y="385"> </location>
|
||||
<location seat="4" x="588" y="425"> </location>
|
||||
<location seat="5" x="92" y="425"> </location>
|
||||
<location seat="6" x="0" y="373"> </location>
|
||||
<location seat="7" x="0" y="223"> </location>
|
||||
<location seat="8" x="25" y="50"> </location>
|
||||
</layout>
|
||||
<layout fav_seat="0" height="547" max="6" width="794">
|
||||
<location seat="1" x="640" y="58"> </location>
|
||||
<location seat="2" x="654" y="288"> </location>
|
||||
<location seat="3" x="615" y="424"> </location>
|
||||
<location seat="4" x="70" y="421"> </location>
|
||||
<location seat="5" x="0" y="280"> </location>
|
||||
<location seat="6" x="70" y="58"> </location>
|
||||
</layout>
|
||||
<layout fav_seat="0" height="547" max="2" width="794">
|
||||
<location seat="1" x="651" y="288"> </location>
|
||||
<location seat="2" x="10" y="288"> </location>
|
||||
</layout>
|
||||
<layout fav_seat="0" height="547" max="9" width="794">
|
||||
<location seat="1" x="634" y="38"> </location>
|
||||
<location seat="2" x="667" y="184"> </location>
|
||||
<location seat="3" x="667" y="321"> </location>
|
||||
<location seat="4" x="667" y="445"> </location>
|
||||
<location seat="5" x="337" y="459"> </location>
|
||||
<location seat="6" x="0" y="400"> </location>
|
||||
<location seat="7" x="0" y="322"> </location>
|
||||
<location seat="8" x="0" y="181"> </location>
|
||||
<location seat="9" x="70" y="53"> </location>
|
||||
</layout>
|
||||
</site>
|
||||
</supported_sites>
|
||||
|
||||
<supported_games>
|
||||
|
@ -585,11 +622,12 @@ Left-Drag to Move"
|
|||
<hhc site="PartyPoker" converter="PartyPokerToFpdb"/>
|
||||
<hhc site="Betfair" converter="BetfairToFpdb"/>
|
||||
<hhc site="Partouche" converter="PartoucheToFpdb"/>
|
||||
<hhc site="Carbon" converter="CarbonToFpdb"/>
|
||||
</hhcs>
|
||||
|
||||
<supported_databases>
|
||||
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database>
|
||||
<!-- <database db_ip="localhost" db_name="fpdb" db_pass="fpdb" db_server="sqlite" db_user="fpdb"/> -->
|
||||
<!-- <database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database> -->
|
||||
<database db_ip="localhost" db_server="sqlite" db_name="fpdb.db3" db_user="fpdb" db_pass="fpdb"/>
|
||||
</supported_databases>
|
||||
|
||||
</FreePokerToolsConfig>
|
||||
|
|
|
@ -36,9 +36,7 @@ import traceback
|
|||
(options, argv) = Options.fpdb_options()
|
||||
|
||||
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_."
|
||||
errorFile = open('HUD-error.txt', 'w', 0)
|
||||
sys.stderr = errorFile
|
||||
print "Note: error output is being logged. Any major error will be reported there _only_."
|
||||
|
||||
import thread
|
||||
import time
|
||||
|
@ -52,6 +50,13 @@ import gobject
|
|||
|
||||
# FreePokerTools modules
|
||||
import Configuration
|
||||
|
||||
print "start logging"
|
||||
log = Configuration.get_logger("logging.conf", config = 'hud')
|
||||
log.debug("%s logger initialized." % "dud")
|
||||
print "logging started"
|
||||
|
||||
|
||||
import Database
|
||||
from HandHistoryConverter import getTableTitleRe
|
||||
# get the correct module for the current os
|
||||
|
@ -72,6 +77,7 @@ class HUD_main(object):
|
|||
|
||||
def __init__(self, db_name = 'fpdb'):
|
||||
self.db_name = db_name
|
||||
self.log = log
|
||||
self.config = Configuration.Config(file=options.config, dbname=options.dbname)
|
||||
self.hud_dict = {}
|
||||
self.hud_params = self.config.get_hud_ui_parameters()
|
||||
|
@ -91,6 +97,7 @@ class HUD_main(object):
|
|||
self.main_window.show_all()
|
||||
|
||||
def destroy(self, *args): # call back for terminating the main eventloop
|
||||
self.log.info("Terminating normally.")
|
||||
gtk.main_quit()
|
||||
|
||||
def kill_hud(self, event, table):
|
||||
|
@ -198,6 +205,7 @@ class HUD_main(object):
|
|||
t0 = time.time()
|
||||
t1 = t2 = t3 = t4 = t5 = t6 = t0
|
||||
new_hand_id = string.rstrip(new_hand_id)
|
||||
self.log.debug("Received hand no %s" % new_hand_id)
|
||||
if new_hand_id == "": # blank line means quit
|
||||
self.destroy()
|
||||
break # this thread is not always killed immediately with gtk.main_quit()
|
||||
|
@ -207,9 +215,8 @@ class HUD_main(object):
|
|||
try:
|
||||
(table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \
|
||||
self.db_connection.get_table_info(new_hand_id)
|
||||
except Exception, err: # TODO: we need to make this a much less generic Exception lulz
|
||||
print "db error: skipping %s" % new_hand_id
|
||||
sys.stderr.write("Database error: could not find hand %s.\n" % new_hand_id)
|
||||
except Exception, err:
|
||||
self.log.error("db error: skipping %s" % new_hand_id)
|
||||
continue
|
||||
t1 = time.time()
|
||||
|
||||
|
@ -267,7 +274,8 @@ class HUD_main(object):
|
|||
# If no client window is found on the screen, complain and continue
|
||||
if type == "tour":
|
||||
table_name = "%s %s" % (tour_number, tab_number)
|
||||
sys.stderr.write("HUD create: table name "+table_name+" not found, skipping.\n")
|
||||
# sys.stderr.write("HUD create: table name "+table_name+" not found, skipping.\n")
|
||||
self.log.error("HUD create: table name %s not found, skipping." % table_name)
|
||||
else:
|
||||
tablewindow.max = max
|
||||
tablewindow.site = site_name
|
||||
|
@ -284,8 +292,8 @@ class HUD_main(object):
|
|||
|
||||
if __name__== "__main__":
|
||||
|
||||
sys.stderr.write("HUD_main starting\n")
|
||||
sys.stderr.write("Using db name = %s\n" % (options.dbname))
|
||||
log.info("HUD_main starting")
|
||||
log.info("Using db name = %s" % (options.dbname))
|
||||
|
||||
# start the HUD_main object
|
||||
hm = HUD_main(db_name = options.dbname)
|
||||
|
|
|
@ -43,7 +43,7 @@ class Hand(object):
|
|||
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
|
||||
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
|
||||
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'}
|
||||
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
|
||||
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9, 'Partouche':10, 'Carbon':11 }
|
||||
|
||||
|
||||
def __init__(self, sitename, gametype, handText, builtFrom = "HHC"):
|
||||
|
@ -205,7 +205,7 @@ dealt whether they were seen in a 'dealt to' line
|
|||
def insert(self, db):
|
||||
""" Function to insert Hand into database
|
||||
Should not commit, and do minimal selects. Callers may want to cache commits
|
||||
db: a connected fpdb_db object"""
|
||||
db: a connected Database object"""
|
||||
|
||||
|
||||
self.stats.getStats(self)
|
||||
|
@ -289,6 +289,24 @@ If a player has None chips he won't be added."""
|
|||
c = c.replace(k,v)
|
||||
return c
|
||||
|
||||
def addAllIn(self, street, player, amount):
|
||||
"""\
|
||||
For sites (currently only Carbon Poker) which record "all in" as a special action, which can mean either "calls and is all in" or "raises all in".
|
||||
"""
|
||||
self.checkPlayerExists(player)
|
||||
amount = re.sub(u',', u'', amount) #some sites have commas
|
||||
Ai = Decimal(amount)
|
||||
Bp = self.lastBet[street]
|
||||
Bc = reduce(operator.add, self.bets[street][player], 0)
|
||||
C = Bp - Bc
|
||||
if Ai <= C:
|
||||
self.addCall(street, player, amount)
|
||||
elif Bp == 0:
|
||||
self.addBet(street, player, amount)
|
||||
else:
|
||||
Rb = Ai - C
|
||||
self._addRaise(street, player, C, Rb, Ai)
|
||||
|
||||
def addAnte(self, player, ante):
|
||||
log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
|
||||
if player is not None:
|
||||
|
@ -396,7 +414,7 @@ Add a raise on [street] by [player] to [amountTo]
|
|||
Bc = reduce(operator.add, self.bets[street][player], 0)
|
||||
Rt = Decimal(amountTo)
|
||||
C = Bp - Bc
|
||||
Rb = Rt - C
|
||||
Rb = Rt - C - Bc
|
||||
self._addRaise(street, player, C, Rb, Rt)
|
||||
|
||||
def _addRaise(self, street, player, C, Rb, Rt):
|
||||
|
|
|
@ -26,29 +26,21 @@ from HandHistoryConverter import *
|
|||
|
||||
# PartyPoker HH Format
|
||||
|
||||
class PartyPokerParseError(FpdbParseError):
|
||||
"Usage: raise PartyPokerParseError(<msg>[, hh=<hh>][, hid=<hid>])"
|
||||
class FpdbParseError(FpdbParseError):
|
||||
"Usage: raise FpdbParseError(<msg>[, hh=<hh>][, hid=<hid>])"
|
||||
|
||||
def __init__(self, msg='', hh=None, hid=None):
|
||||
if hh is not None:
|
||||
msg += "\n\nHand history attached below:\n" + self.wrapHh(hh)
|
||||
return super(PartyPokerParseError, self).__init__(msg, hid=hid)
|
||||
return super(FpdbParseError, self).__init__(msg, hid=hid)
|
||||
|
||||
def wrapHh(self, hh):
|
||||
return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
|
||||
{'DELIMETER': '#'*50, 'HH': hh}
|
||||
|
||||
class PartyPoker(HandHistoryConverter):
|
||||
|
||||
############################################################
|
||||
# Class Variables
|
||||
|
||||
sitename = "PartyPoker"
|
||||
codepage = "cp1252"
|
||||
siteId = 9 # TODO: automate; it's a class variable so shouldn't hit DB too often
|
||||
filetype = "text" # "text" or "xml". I propose we subclass HHC to HHC_Text and HHC_XML.
|
||||
|
||||
|
||||
siteId = 9
|
||||
filetype = "text"
|
||||
sym = {'USD': "\$", }
|
||||
|
||||
# Static regexes
|
||||
|
@ -92,9 +84,9 @@ class PartyPoker(HandHistoryConverter):
|
|||
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
|
||||
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton
|
||||
""",
|
||||
re.MULTILINE|re.VERBOSE)
|
||||
re.VERBOSE|re.MULTILINE)
|
||||
|
||||
# re_TotalPlayers = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<MAXSEATS>\d+)", re.MULTILINE)
|
||||
re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE)
|
||||
re_SplitHands = re.compile('\x00+')
|
||||
re_TailSplitHands = re.compile('(\x00+)')
|
||||
lineSplitter = '\n'
|
||||
|
@ -131,16 +123,12 @@ class PartyPoker(HandHistoryConverter):
|
|||
'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
|
||||
for key in ('CUR_SYM', 'CUR'):
|
||||
subst[key] = re.escape(subst[key])
|
||||
log.debug("player_re: '%s'" % subst['PLYR'])
|
||||
log.debug("CUR_SYM: '%s'" % subst['CUR_SYM'])
|
||||
log.debug("CUR: '%s'" % subst['CUR'])
|
||||
self.re_PostSB = re.compile(
|
||||
r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s\]\." % subst,
|
||||
re.MULTILINE)
|
||||
self.re_PostBB = re.compile(
|
||||
r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s\]\." % subst,
|
||||
re.MULTILINE)
|
||||
# NOTE: comma is used as a fraction part delimeter in re below
|
||||
self.re_PostDead = re.compile(
|
||||
r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
|
||||
re.MULTILINE)
|
||||
|
@ -195,8 +183,6 @@ class PartyPoker(HandHistoryConverter):
|
|||
gametype dict is:
|
||||
{'limitType': xxx, 'base': xxx, 'category': xxx}"""
|
||||
|
||||
log.debug(PartyPokerParseError().wrapHh( handText ))
|
||||
|
||||
info = {}
|
||||
m = self._getGameType(handText)
|
||||
if m is None:
|
||||
|
@ -213,22 +199,16 @@ class PartyPoker(HandHistoryConverter):
|
|||
|
||||
for expectedField in ['LIMIT', 'GAME']:
|
||||
if mg[expectedField] is None:
|
||||
raise PartyPokerParseError(
|
||||
"Cannot fetch field '%s'" % expectedField,
|
||||
hh = handText)
|
||||
raise FpdbParseError( "Cannot fetch field '%s'" % expectedField)
|
||||
try:
|
||||
info['limitType'] = limits[mg['LIMIT'].strip()]
|
||||
except:
|
||||
raise PartyPokerParseError(
|
||||
"Unknown limit '%s'" % mg['LIMIT'],
|
||||
hh = handText)
|
||||
raise FpdbParseError("Unknown limit '%s'" % mg['LIMIT'])
|
||||
|
||||
try:
|
||||
(info['base'], info['category']) = games[mg['GAME']]
|
||||
except:
|
||||
raise PartyPokerParseError(
|
||||
"Unknown game type '%s'" % mg['GAME'],
|
||||
hh = handText)
|
||||
raise FpdbParseError("Unknown game type '%s'" % mg['GAME'])
|
||||
|
||||
if 'TOURNO' in mg:
|
||||
info['type'] = 'tour'
|
||||
|
@ -251,23 +231,21 @@ class PartyPoker(HandHistoryConverter):
|
|||
try:
|
||||
info.update(self.re_Hid.search(hand.handText).groupdict())
|
||||
except:
|
||||
raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText)
|
||||
raise FpdbParseError("Cannot read HID for current hand")
|
||||
|
||||
try:
|
||||
info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
|
||||
except:
|
||||
raise PartyPokerParseError("Cannot read Handinfo for current hand",
|
||||
hh=hand.handText, hid = info['HID'])
|
||||
raise FpdbParseError("Cannot read Handinfo for current hand", hid = info['HID'])
|
||||
|
||||
try:
|
||||
info.update(self._getGameType(hand.handText).groupdict())
|
||||
except:
|
||||
raise PartyPokerParseError("Cannot read GameType for current hand",
|
||||
hh=hand.handText, hid = info['HID'])
|
||||
raise FpdbParseError("Cannot read GameType for current hand", hid = info['HID'])
|
||||
|
||||
|
||||
# m = self.re_TotalPlayers.search(hand.handText)
|
||||
# if m: info.update(m.groupdict())
|
||||
m = self.re_CountedSeats.search(hand.handText)
|
||||
if m: info.update(m.groupdict())
|
||||
|
||||
|
||||
# FIXME: it's dirty hack
|
||||
|
@ -294,6 +272,7 @@ class PartyPoker(HandHistoryConverter):
|
|||
if key == 'DATETIME':
|
||||
#Saturday, July 25, 07:53:52 EDT 2009
|
||||
#Thursday, July 30, 21:40:41 MSKS 2009
|
||||
#Sunday, October 25, 13:39:07 MSK 2009
|
||||
m2 = re.search("\w+, (?P<M>\w+) (?P<D>\d+), (?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+) (?P<TZ>[A-Z]+) (?P<Y>\d+)", info[key])
|
||||
# we cant use '%B' due to locale problems
|
||||
months = ['January', 'February', 'March', 'April','May', 'June',
|
||||
|
@ -317,6 +296,10 @@ class PartyPoker(HandHistoryConverter):
|
|||
hand.buttonpos = info[key]
|
||||
if key == 'TOURNO':
|
||||
hand.tourNo = info[key]
|
||||
if key == 'TABLE_ID_WRAPPER':
|
||||
if info[key] == '#':
|
||||
# FIXME: there is no such property in Hand class
|
||||
self.isSNG = True
|
||||
if key == 'BUYIN':
|
||||
# FIXME: it's dirty hack T_T
|
||||
# code below assumes that tournament rake is equal to zero
|
||||
|
@ -328,7 +311,7 @@ class PartyPoker(HandHistoryConverter):
|
|||
if key == 'LEVEL':
|
||||
hand.level = info[key]
|
||||
if key == 'PLAY' and info['PLAY'] != 'Real':
|
||||
# if realy there's no play money hh on party
|
||||
# if realy party doesn's save play money hh
|
||||
hand.gametype['currency'] = 'play'
|
||||
|
||||
def readButton(self, hand):
|
||||
|
@ -413,8 +396,6 @@ class PartyPoker(HandHistoryConverter):
|
|||
blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
|
||||
hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
|
||||
|
||||
|
||||
|
||||
def readHeroCards(self, hand):
|
||||
# we need to grab hero's cards
|
||||
for street in ('PREFLOP',):
|
||||
|
@ -425,7 +406,6 @@ class PartyPoker(HandHistoryConverter):
|
|||
newcards = renderCards(found.group('NEWCARDS'))
|
||||
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
|
||||
|
||||
|
||||
def readAction(self, hand, street):
|
||||
m = self.re_Action.finditer(hand.streets[street])
|
||||
for action in m:
|
||||
|
@ -460,10 +440,9 @@ class PartyPoker(HandHistoryConverter):
|
|||
elif actionType == 'checks':
|
||||
hand.addCheck( street, playerName )
|
||||
else:
|
||||
raise PartyPokerParseError(
|
||||
raise FpdbParseError(
|
||||
"Unimplemented readAction: '%s' '%s'" % (playerName,actionType,),
|
||||
hid = hand.hid, hh = hand.handText )
|
||||
|
||||
hid = hand.hid, )
|
||||
|
||||
def readShowdownActions(self, hand):
|
||||
# all action in readShownCards
|
||||
|
@ -482,6 +461,17 @@ class PartyPoker(HandHistoryConverter):
|
|||
|
||||
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked)
|
||||
|
||||
@staticmethod
|
||||
def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
|
||||
"Returns string to search in windows titles"
|
||||
if type=="tour":
|
||||
print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (table_name, table_number)
|
||||
return "%s.+Table\s#%s" % (table_name, table_number)
|
||||
else:
|
||||
print 'party', 'getTableTitleRe', table_number
|
||||
return table_name
|
||||
|
||||
|
||||
def ringBlinds(ringLimit):
|
||||
"Returns blinds for current limit in cash games"
|
||||
ringLimit = float(clearMoneyString(ringLimit))
|
||||
|
|
|
@ -240,7 +240,6 @@ class PokerStars(HandHistoryConverter):
|
|||
def readPlayerStacks(self, hand):
|
||||
log.debug("readPlayerStacks")
|
||||
m = self.re_PlayerInfo.finditer(hand.handText)
|
||||
players = []
|
||||
for a in m:
|
||||
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
|
||||
|
||||
|
|
|
@ -58,6 +58,27 @@ class Sql:
|
|||
self.query['drop_table'] = """DROP TABLE IF EXISTS """
|
||||
|
||||
|
||||
##################################################################
|
||||
# Set transaction isolation level
|
||||
##################################################################
|
||||
|
||||
if db_server == 'mysql' or db_server == 'postgresql':
|
||||
self.query['set tx level'] = """SET SESSION TRANSACTION
|
||||
ISOLATION LEVEL READ COMMITTED"""
|
||||
elif db_server == 'sqlite':
|
||||
self.query['set tx level'] = """ """
|
||||
|
||||
|
||||
################################
|
||||
# Select basic info
|
||||
################################
|
||||
|
||||
self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
|
||||
|
||||
self.query['getGames'] = """SELECT DISTINCT category from Gametypes"""
|
||||
|
||||
self.query['getLimits'] = """SELECT DISTINCT bigBlind from Gametypes ORDER by bigBlind DESC"""
|
||||
|
||||
################################
|
||||
# Create Settings
|
||||
################################
|
||||
|
@ -214,6 +235,7 @@ class Sql:
|
|||
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id),
|
||||
tableName VARCHAR(22) NOT NULL,
|
||||
siteHandNo BIGINT NOT NULL,
|
||||
tourneyId INT UNSIGNED NOT NULL,
|
||||
gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
|
||||
handStart DATETIME NOT NULL,
|
||||
importTime DATETIME NOT NULL,
|
||||
|
@ -249,6 +271,7 @@ class Sql:
|
|||
id BIGSERIAL, PRIMARY KEY (id),
|
||||
tableName VARCHAR(22) NOT NULL,
|
||||
siteHandNo BIGINT NOT NULL,
|
||||
tourneyId INT NOT NULL,
|
||||
gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
|
||||
handStart timestamp without time zone NOT NULL,
|
||||
importTime timestamp without time zone NOT NULL,
|
||||
|
@ -283,6 +306,7 @@ class Sql:
|
|||
id INTEGER PRIMARY KEY,
|
||||
tableName TEXT(22) NOT NULL,
|
||||
siteHandNo INT NOT NULL,
|
||||
tourneyId INT NOT NULL,
|
||||
gametypeId INT NOT NULL,
|
||||
handStart REAL NOT NULL,
|
||||
importTime REAL NOT NULL,
|
||||
|
@ -3437,6 +3461,7 @@ class Sql:
|
|||
tablename,
|
||||
gametypeid,
|
||||
sitehandno,
|
||||
tourneyId,
|
||||
handstart,
|
||||
importtime,
|
||||
seats,
|
||||
|
@ -3467,7 +3492,7 @@ class Sql:
|
|||
VALUES
|
||||
(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s)"""
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
|
||||
|
||||
|
||||
self.query['store_hands_players'] = """INSERT INTO HandsPlayers (
|
||||
|
|
|
@ -185,7 +185,7 @@ class Tourney(object):
|
|||
def old_insert_from_Hand(self, db):
|
||||
""" Function to insert Hand into database
|
||||
Should not commit, and do minimal selects. Callers may want to cache commits
|
||||
db: a connected fpdb_db object"""
|
||||
db: a connected Database object"""
|
||||
# TODO:
|
||||
# Players - base playerid and siteid tuple
|
||||
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
|
||||
|
|
|
@ -112,7 +112,6 @@ import GuiGraphViewer
|
|||
import GuiSessionViewer
|
||||
import SQL
|
||||
import Database
|
||||
import FpdbSQLQueries
|
||||
import Configuration
|
||||
import Exceptions
|
||||
|
||||
|
|
|
@ -16,207 +16,6 @@
|
|||
#In the "official" distribution you can find the license in
|
||||
#agpl-3.0.txt in the docs folder of the package.
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
import math
|
||||
from time import time, strftime
|
||||
from Exceptions import *
|
||||
|
||||
try:
|
||||
import sqlalchemy.pool as pool
|
||||
use_pool = True
|
||||
except ImportError:
|
||||
logging.info("Not using sqlalchemy connection pool.")
|
||||
use_pool = False
|
||||
|
||||
try:
|
||||
from numpy import var
|
||||
use_numpy = True
|
||||
except ImportError:
|
||||
logging.info("Not using numpy to define variance in sqlite.")
|
||||
use_numpy = False
|
||||
|
||||
import FpdbSQLQueries
|
||||
import Configuration
|
||||
|
||||
# Variance created as sqlite has a bunch of undefined aggregate functions.
|
||||
|
||||
class VARIANCE:
|
||||
def __init__(self):
|
||||
self.store = []
|
||||
|
||||
def step(self, value):
|
||||
self.store.append(value)
|
||||
|
||||
def finalize(self):
|
||||
return float(var(self.store))
|
||||
|
||||
class sqlitemath:
|
||||
def mod(self, a, b):
|
||||
return a%b
|
||||
|
||||
class fpdb_db:
|
||||
MYSQL_INNODB = 2
|
||||
PGSQL = 3
|
||||
SQLITE = 4
|
||||
|
||||
def __init__(self):
|
||||
"""Simple constructor, doesnt really do anything"""
|
||||
self.db = None
|
||||
self.cursor = None
|
||||
self.sql = {}
|
||||
#end def __init__
|
||||
|
||||
def do_connect(self, config=None):
|
||||
"""Connects a database using information in config"""
|
||||
if config is None:
|
||||
raise FpdbError('Configuration not defined')
|
||||
|
||||
self.settings = {}
|
||||
self.settings['os'] = "linuxmac" if os.name != "nt" else "windows"
|
||||
|
||||
db = config.get_db_parameters()
|
||||
self.connect(backend=db['db-backend'],
|
||||
host=db['db-host'],
|
||||
database=db['db-databaseName'],
|
||||
user=db['db-user'],
|
||||
password=db['db-password'])
|
||||
#end def do_connect
|
||||
|
||||
def connect(self, backend=None, host=None, database=None,
|
||||
user=None, password=None):
|
||||
"""Connects a database with the given parameters"""
|
||||
if backend is None:
|
||||
raise FpdbError('Database backend not defined')
|
||||
self.backend = backend
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.database = database
|
||||
if backend == fpdb_db.MYSQL_INNODB:
|
||||
import MySQLdb
|
||||
if use_pool:
|
||||
MySQLdb = pool.manage(MySQLdb, pool_size=5)
|
||||
try:
|
||||
self.db = MySQLdb.connect(host=host, user=user, passwd=password, db=database, use_unicode=True)
|
||||
#TODO: Add port option
|
||||
except MySQLdb.Error, ex:
|
||||
if ex.args[0] == 1045:
|
||||
raise FpdbMySQLAccessDenied(ex.args[0], ex.args[1])
|
||||
elif ex.args[0] == 2002 or ex.args[0] == 2003: # 2002 is no unix socket, 2003 is no tcp socket
|
||||
raise FpdbMySQLNoDatabase(ex.args[0], ex.args[1])
|
||||
else:
|
||||
print "*** WARNING UNKNOWN MYSQL ERROR", ex
|
||||
elif backend == fpdb_db.PGSQL:
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
if use_pool:
|
||||
psycopg2 = pool.manage(psycopg2, pool_size=5)
|
||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||
# If DB connection is made over TCP, then the variables
|
||||
# host, user and password are required
|
||||
# For local domain-socket connections, only DB name is
|
||||
# needed, and everything else is in fact undefined and/or
|
||||
# flat out wrong
|
||||
# sqlcoder: This database only connect failed in my windows setup??
|
||||
# Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere?
|
||||
connected = False
|
||||
if self.host == "localhost" or self.host == "127.0.0.1":
|
||||
try:
|
||||
self.db = psycopg2.connect(database = database)
|
||||
connected = True
|
||||
except:
|
||||
# direct connection failed so try user/pass/... version
|
||||
pass
|
||||
if not connected:
|
||||
try:
|
||||
self.db = psycopg2.connect(host = host,
|
||||
user = user,
|
||||
password = password,
|
||||
database = database)
|
||||
except Exception, ex:
|
||||
if 'Connection refused' in ex.args[0]:
|
||||
# meaning eg. db not running
|
||||
raise FpdbPostgresqlNoDatabase(errmsg = ex.args[0])
|
||||
elif 'password authentication' in ex.args[0]:
|
||||
raise FpdbPostgresqlAccessDenied(errmsg = ex.args[0])
|
||||
else:
|
||||
msg = ex.args[0]
|
||||
print msg
|
||||
raise FpdbError(msg)
|
||||
elif backend == fpdb_db.SQLITE:
|
||||
logging.info("Connecting to SQLite:%(database)s" % {'database':database})
|
||||
import sqlite3
|
||||
if use_pool:
|
||||
sqlite3 = pool.manage(sqlite3, pool_size=1)
|
||||
else:
|
||||
logging.warning("SQLite won't work well without 'sqlalchemy' installed.")
|
||||
|
||||
if not os.path.isdir(Configuration.DIR_DATABASES) and not database == ":memory:":
|
||||
print "Creating directory: '%s'" % (Configuration.DIR_DATABASES)
|
||||
os.mkdir(Configuration.DIR_DATABASES)
|
||||
database = os.path.join(Configuration.DIR_DATABASES, database)
|
||||
self.db = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES )
|
||||
sqlite3.register_converter("bool", lambda x: bool(int(x)))
|
||||
sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
|
||||
self.db.create_function("floor", 1, math.floor)
|
||||
tmp = sqlitemath()
|
||||
self.db.create_function("mod", 2, tmp.mod)
|
||||
if use_numpy:
|
||||
self.db.create_aggregate("variance", 1, VARIANCE)
|
||||
else:
|
||||
logging.warning("Some database functions will not work without NumPy support")
|
||||
else:
|
||||
raise FpdbError("unrecognised database backend:"+backend)
|
||||
|
||||
self.cursor = self.db.cursor()
|
||||
# Set up query dictionary as early in the connection process as we can.
|
||||
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
|
||||
self.cursor.execute(self.sql.query['set tx level'])
|
||||
self.wrongDbVersion = False
|
||||
try:
|
||||
self.cursor.execute("SELECT * FROM Settings")
|
||||
settings = self.cursor.fetchone()
|
||||
if settings[0] != 118:
|
||||
print "outdated or too new database version - please recreate tables"
|
||||
self.wrongDbVersion = True
|
||||
except:# _mysql_exceptions.ProgrammingError:
|
||||
if database != ":memory:": print "failed to read settings table - please recreate tables"
|
||||
self.wrongDbVersion = True
|
||||
#end def connect
|
||||
|
||||
def disconnect(self, due_to_error=False):
|
||||
"""Disconnects the DB"""
|
||||
if due_to_error:
|
||||
self.db.rollback()
|
||||
else:
|
||||
self.db.commit()
|
||||
self.cursor.close()
|
||||
self.db.close()
|
||||
#end def disconnect
|
||||
|
||||
def reconnect(self, due_to_error=False):
|
||||
"""Reconnects the DB"""
|
||||
#print "started fpdb_db.reconnect"
|
||||
self.disconnect(due_to_error)
|
||||
self.connect(self.backend, self.host, self.database, self.user, self.password)
|
||||
|
||||
def get_backend_name(self):
|
||||
"""Returns the name of the currently used backend"""
|
||||
if self.backend==2:
|
||||
return "MySQL InnoDB"
|
||||
elif self.backend==3:
|
||||
return "PostgreSQL"
|
||||
elif self.backend==4:
|
||||
return "SQLite"
|
||||
else:
|
||||
raise FpdbError("invalid backend")
|
||||
#end def get_backend_name
|
||||
|
||||
def get_db_info(self):
|
||||
return (self.host, self.database, self.user, self.password)
|
||||
#end def get_db_info
|
||||
|
||||
#end class fpdb_db
|
||||
|
|
|
@ -35,7 +35,6 @@ import gtk
|
|||
|
||||
# fpdb/FreePokerTools modules
|
||||
|
||||
import fpdb_db
|
||||
import Database
|
||||
import Configuration
|
||||
import Exceptions
|
||||
|
|
0
pyfpdb/test_PokerStars.py
Normal file → Executable file
0
pyfpdb/test_PokerStars.py
Normal file → Executable file
5
run_fpdb.py
Executable file → Normal file
5
run_fpdb.py
Executable file → Normal file
|
@ -19,8 +19,11 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
# sys.path[0] holds the dir run_fpdb.py was in
|
||||
# sys.path[0] holds the directory run_fpdb.py is in
|
||||
sys.path[0] = sys.path[0]+os.sep+"pyfpdb"
|
||||
os.chdir(sys.path[0])
|
||||
#print "sys.path[0] =", sys.path[0], "cwd =", os.getcwd()
|
||||
|
||||
|
||||
import fpdb
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user