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

This commit is contained in:
Gerko de Roo 2010-01-28 17:59:23 +01:00
commit cfdec93d36
19 changed files with 689 additions and 408 deletions

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python #!/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 # 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
@ -18,93 +19,286 @@
######################################################################## ########################################################################
# Standard Library modules # This code is based heavily on EverleafToFpdb.py, by Carl Gherardi
import Configuration #
import traceback # 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 sys
import re import logging
import xml.dom.minidom from HandHistoryConverter import *
from xml.dom.minidom import Node from decimal import Decimal
from HandHistoryConverter import HandHistoryConverter
# Carbon format looks like: class Carbon(HandHistoryConverter):
# 1) <description type="Holdem" stakes="No Limit ($0.25/$0.50)"/> sitename = "Carbon"
# 2) <game id="14902583-5578" starttime="20081006145401" numholecards="2" gametype="2" realmoney="true" data="20081006|Niagara Falls (14902583)|14902583|14902583-5578|false"> filetype = "text"
# 3) <players dealer="8"> codepage = "cp1252"
# <player seat="3" nickname="PlayerInSeat3" balance="$43.29" dealtin="true" /> siteID = 11
# ...
# 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: # Static regexes
# BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME re_SplitHands = re.compile(r'</game>\n+(?=<game)')
# This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_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 compilePlayerRegexs(self, hand):
def __init__(self, config, filename): pass
print "Initialising Carbon Poker converter class"
HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init def playerNameFromSeatNo(self, seatNo, hand):
self.setFileType("xml") # This special function is required because Carbon Poker records
self.siteId = 4 # Needs to match id entry in Sites database # 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): def readSupportedGames(self):
pass return [["ring", "hold", "nl"],
def determineGameType(self): ["tour", "hold", "nl"]]
gametype = []
desc_node = self.doc.getElementsByTagName("description") def determineGameType(self, handText):
#TODO: no examples of non ring type yet """return dict with keys/values:
gametype = gametype + ["ring"] 'type' in ('ring', 'tour')
type = desc_node[0].getAttribute("type") 'limitType' in ('nl', 'cn', 'pl', 'cp', 'fl')
if(type == "Holdem"): 'base' in ('hold', 'stud', 'draw')
gametype = gametype + ["hold"] '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: else:
print "Carbon: Unknown gametype: '%s'" % (type) self.info['type'] = 'ring'
self.info['currency'] = 'USD'
stakes = desc_node[0].getAttribute("stakes") return self.info
#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"): def readHandInfo(self, hand):
gametype = gametype + ["nl"] 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'))] def readPlayerStacks(self, hand):
gametype = gametype + [self.float2int(m.group('BB'))] 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): def readCommunityCards(self, hand, street):
pass m = self.re_Board.search(hand.streets[street])
def readBlinds(self): if street == 'FLOP':
pass hand.setCommunityCards(street, m.group('CARDS').split(','))
def readAction(self): elif street in ('TURN','RIVER'):
pass hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]])
# Override read function as xml.minidom barfs on the Carbon layout def readAntes(self, hand):
# This is pretty dodgy pass # ???
def readFile(self, filename):
print "Carbon: Reading file: '%s'" %(filename) def readBringIn(self, hand):
infile=open(filename, "rU") pass # ???
self.obs = infile.read()
infile.close() def readBlinds(self, hand):
self.obs = "<CarbonHHFile>\n" + self.obs + "</CarbonHHFile>"
try: try:
doc = xml.dom.minidom.parseString(self.obs) m = self.re_PostSB.search(hand.handText)
self.doc = doc hand.addBlind(self.playerNameFromSeatNo(m.group('PSEAT'), hand),
except: 'small blind', m.group('SB'))
traceback.print_exc(file=sys.stderr) 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__": if __name__ == "__main__":
c = Configuration.Config() parser = OptionParser()
e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml") parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-")
e.processFile() parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
print str(e) 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)

View File

@ -56,33 +56,33 @@ def get_exec_path():
if hasattr(sys, "frozen"): # compiled by py2exe if hasattr(sys, "frozen"): # compiled by py2exe
return os.path.dirname(sys.executable) return os.path.dirname(sys.executable)
else: else:
pathname = os.path.dirname(sys.argv[0]) return sys.path[0]
return os.path.abspath(pathname)
def get_config(file_name, fallback = True): def get_config(file_name, fallback = True):
"""Looks in cwd and in self.default_config_path for a config file.""" """Looks in exec dir and in self.default_config_path for a config file."""
config_path = os.path.join(get_exec_path(), file_name) config_path = os.path.join(DIR_SELF, file_name) # look in exec dir
# print "config_path=", config_path if os.path.exists(config_path) and os.path.isfile(config_path):
if os.path.exists(config_path): # there is a file in the cwd return config_path # there is a file in the exec dir so we use it
return config_path # so we use it else:
else: # no file in the cwd, look where it should be in the first place config_path = os.path.join(DIR_CONFIG, file_name) # look in config dir
config_path = os.path.join(get_default_config_path(), file_name) if os.path.exists(config_path) and os.path.isfile(config_path):
# print "config path 2=", config_path
if os.path.exists(config_path):
return config_path return config_path
# No file found # No file found
if not fallback: if not fallback:
return False return False
# OK, fall back to the .example file, should be in the start dir # OK, fall back to the .example file, should be in the exec dir
if os.path.exists(file_name + ".example"): if os.path.exists(os.path.join(DIR_SELF, file_name + ".example")):
try: 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 "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 print "A %s file has been created. You will probably have to edit it." % os.path.join(DIR_CONFIG, file_name)
sys.stderr.write("No %s found, using %s.example.\n" % (file_name, file_name) ) log.error("No %s found, using %s.example.\n" % (file_name, file_name) )
except: 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 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.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name)
sys.exit() sys.exit()
@ -94,18 +94,26 @@ def get_logger(file_name, config = "config", fallback = False):
try: try:
logging.config.fileConfig(conf) logging.config.fileConfig(conf)
log = logging.getLogger(config) log = logging.getLogger(config)
log.debug("%s logger initialised" % config)
return log return log
except: except:
pass pass
log = logging.basicConfig() log = logging.basicConfig()
log = logging.getLogger() log = logging.getLogger()
log.debug("config logger initialised") log.error("basicConfig logger initialised")
return log return log
# find a logging.conf file and set up logging def check_dir(path, create = True):
log = get_logger("logging.conf") """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 # application wide consts
@ -113,19 +121,31 @@ log = get_logger("logging.conf")
APPLICATION_NAME_SHORT = 'fpdb' APPLICATION_NAME_SHORT = 'fpdb'
APPLICATION_VERSION = 'xx.xx.xx' APPLICATION_VERSION = 'xx.xx.xx'
DIR_SELF = os.path.dirname(get_exec_path()) DIR_SELF = get_exec_path()
#TODO: imo no good idea to place 'database' in parent dir DIR_CONFIG = check_dir(get_default_config_path())
DIR_DATABASES = os.path.join(os.path.dirname(DIR_SELF), 'database') 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_POSTGRESQL = 'postgresql'
DATABASE_TYPE_SQLITE = 'sqlite' DATABASE_TYPE_SQLITE = 'sqlite'
DATABASE_TYPE_MYSQL = 'mysql' DATABASE_TYPE_MYSQL = 'mysql'
#TODO: should this be a tuple or a dict
DATABASE_TYPES = ( DATABASE_TYPES = (
DATABASE_TYPE_POSTGRESQL, DATABASE_TYPE_POSTGRESQL,
DATABASE_TYPE_SQLITE, DATABASE_TYPE_SQLITE,
DATABASE_TYPE_MYSQL, 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] LOCALE_ENCODING = locale.getdefaultlocale()[1]
######################################################################## ########################################################################
@ -408,11 +428,10 @@ class Config:
if file is not None: # config file path passed in if file is not None: # config file path passed in
file = os.path.expanduser(file) file = os.path.expanduser(file)
if not os.path.exists(file): if not os.path.exists(file):
print "Configuration file %s not found. Using defaults." % (file) log.error("Specified configuration file %s not found. Using defaults." % (file))
sys.stderr.write("Configuration file %s not found. Using defaults." % (file))
file = None 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 # 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 # If using the example, we'll edit it later
@ -429,6 +448,8 @@ class Config:
self.doc = doc self.doc = doc
self.file = file self.file = file
self.dir = os.path.dirname(self.file)
self.dir_databases = os.path.join(self.dir, 'database')
self.supported_sites = {} self.supported_sites = {}
self.supported_games = {} self.supported_games = {}
self.supported_databases = {} # databaseName --> Database instance self.supported_databases = {} # databaseName --> Database instance

View File

@ -38,11 +38,31 @@ from decimal import Decimal
import string import string
import re import re
import Queue import Queue
import codecs
import logging
import math
# pyGTK modules # 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 # FreePokerTools modules
import fpdb_db
import Configuration import Configuration
import SQL import SQL
import Card import Card
@ -50,7 +70,29 @@ import Tourney
import Charset import Charset
from Exceptions import * 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: class Database:
@ -188,15 +230,14 @@ class Database:
log.info("Creating Database instance, sql = %s" % sql) log.info("Creating Database instance, sql = %s" % sql)
self.config = c self.config = c
self.__connected = False self.__connected = False
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql self.settings = {}
self.do_connect(c) self.settings['os'] = "linuxmac" if os.name != "nt" else "windows"
db_params = c.get_db_parameters()
if self.backend == self.PGSQL: self.import_options = c.get_import_parameters()
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE self.backend = db_params['db-backend']
#ISOLATION_LEVEL_AUTOCOMMIT = 0 self.db_server = db_params['db-server']
#ISOLATION_LEVEL_READ_COMMITTED = 1 self.database = db_params['db-databaseName']
#ISOLATION_LEVEL_SERIALIZABLE = 2 self.host = db_params['db-host']
# where possible avoid creating new SQL instance by using the global one passed in # where possible avoid creating new SQL instance by using the global one passed in
if sql is None: if sql is None:
@ -204,6 +245,15 @@ class Database:
else: else:
self.sql = sql 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: if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion:
log.info("sqlite/:memory: - creating") log.info("sqlite/:memory: - creating")
self.recreate_tables() self.recreate_tables()
@ -226,8 +276,6 @@ class Database:
self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero 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.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.saveActions = False if self.import_options['saveActions'] == False else True
self.connection.rollback() # make sure any locks taken so far are released self.connection.rollback() # make sure any locks taken so far are released
@ -238,14 +286,20 @@ class Database:
self.hud_style = style self.hud_style = style
def do_connect(self, c): def do_connect(self, c):
if c is None:
raise FpdbError('Configuration not defined')
db = c.get_db_parameters()
try: 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: except:
# error during connect # error during connect
self.__connected = False self.__connected = False
raise raise
self.connection = self.fdb.db
self.wrongDbVersion = self.fdb.wrongDbVersion
db_params = c.get_db_parameters() db_params = c.get_db_parameters()
self.import_options = c.get_import_parameters() self.import_options = c.get_import_parameters()
@ -255,11 +309,137 @@ class Database:
self.host = db_params['db-host'] self.host = db_params['db-host']
self.__connected = True 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): def commit(self):
self.fdb.db.commit() self.connection.commit()
def rollback(self): def rollback(self):
self.fdb.db.rollback() self.connection.rollback()
def connected(self): def connected(self):
return self.__connected return self.__connected
@ -272,11 +452,18 @@ class Database:
def disconnect(self, due_to_error=False): def disconnect(self, due_to_error=False):
"""Disconnects the DB (rolls back if param is true, otherwise commits""" """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): def reconnect(self, due_to_error=False):
"""Reconnects the DB""" """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): def get_backend_name(self):
"""Returns the name of the currently used backend""" """Returns the name of the currently used backend"""
@ -289,6 +476,9 @@ class Database:
else: else:
raise FpdbError("invalid backend") 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): def get_table_name(self, hand_id):
c = self.connection.cursor() c = self.connection.cursor()
c.execute(self.sql.query['get_table_name'], (hand_id, )) c.execute(self.sql.query['get_table_name'], (hand_id, ))
@ -845,6 +1035,7 @@ class Database:
self.create_tables() self.create_tables()
self.createAllIndexes() self.createAllIndexes()
self.commit() self.commit()
print "Finished recreating tables"
log.info("Finished recreating tables") log.info("Finished recreating tables")
#end def recreate_tables #end def recreate_tables
@ -1110,7 +1301,7 @@ class Database:
def fillDefaultData(self): def fillDefaultData(self):
c = self.get_cursor() 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 ('Full Tilt Poker', 'USD')")
c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')")
c.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', '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 ('Absolute', 'USD')")
c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', '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 ('Partouche', 'EUR')")
c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')")
if self.backend == self.SQLITE: if self.backend == self.SQLITE:
c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);") c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
elif self.backend == self.PGSQL: elif self.backend == self.PGSQL:
@ -1266,7 +1458,7 @@ class Database:
try: try:
self.get_cursor().execute(self.sql.query['lockForInsert']) self.get_cursor().execute(self.sql.query['lockForInsert'])
except: 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 #end def lock_for_insert
########################### ###########################
@ -1285,6 +1477,7 @@ class Database:
p['tableName'], p['tableName'],
p['gameTypeId'], p['gameTypeId'],
p['siteHandNo'], p['siteHandNo'],
0, # tourneyId: 0 means not a tourney hand
p['handStart'], p['handStart'],
datetime.today(), #importtime datetime.today(), #importtime
p['seats'], p['seats'],

View File

@ -27,8 +27,8 @@ import gobject
#import pokereval #import pokereval
import Configuration import Configuration
import fpdb_db import Database
import FpdbSQLQueries import SQL
import Charset import Charset
class Filters(threading.Thread): class Filters(threading.Thread):
@ -800,10 +800,10 @@ def main(argv=None):
config = Configuration.Config() config = Configuration.Config()
db = None db = None
db = fpdb_db.fpdb_db() db = Database.Database()
db.do_connect(config) db.do_connect(config)
qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name()) qdict = SQL.SQL(db.get_backend_name())
i = Filters(db, config, qdict) i = Filters(db, config, qdict)
main_window = gtk.Window() main_window = gtk.Window()

View File

@ -18,12 +18,10 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
######################################################################## ########################################################################
import sys
import logging import logging
from HandHistoryConverter import * from HandHistoryConverter import *
# Fulltilt HH Format converter # Fulltilt HH Format converter
# TODO: cat tourno and table to make table name for tournaments
class Fulltilt(HandHistoryConverter): class Fulltilt(HandHistoryConverter):
@ -67,8 +65,8 @@ class Fulltilt(HandHistoryConverter):
(\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+)) (\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+))
''', re.VERBOSE) ''', re.VERBOSE)
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) 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_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>.*) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', 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>.+)\]") re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
#static regex for tourney purpose #static regex for tourney purpose
@ -191,7 +189,6 @@ class Fulltilt(HandHistoryConverter):
if mg['TOURNO'] is None: info['type'] = "ring" if mg['TOURNO'] is None: info['type'] = "ring"
else: info['type'] = "tour" else: info['type'] = "tour"
# NB: SB, BB must be interpreted as blinds or bets depending on limit type. # 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 return info
def readHandInfo(self, hand): 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) #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')), #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'))) ##int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
#FIXME: hand.buttonpos = int(m.group('BUTTON'))
def readPlayerStacks(self, hand): 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" : if hand.gametype['type'] == "ring" :
m = self.re_PlayerInfo.finditer(hand.handText) m = self.re_PlayerInfo.finditer(pre)
else: #if hand.gametype['type'] == "tour" else: #if hand.gametype['type'] == "tour"
m = self.re_TourneyPlayerInfo.finditer(hand.handText) m = self.re_TourneyPlayerInfo.finditer(pre)
players = []
for a in m: for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) 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']] hand.mixed = self.mixes[m.groupdict()['MIXED']]
def readSummaryInfo(self, summaryInfoList): def readSummaryInfo(self, summaryInfoList):
starttime = time.time()
self.status = True self.status = True
m = re.search("Tournament Summary", summaryInfoList[0]) 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'])) tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
else : else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin: 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'])) tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))
if mg['FEE'] is not None: if mg['FEE'] is not None:
if tourney.fee is None: if tourney.fee is None:
tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
else : else :
if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee: 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'])) tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))
if tourney.buyin is None: if tourney.buyin is None:

View File

@ -300,7 +300,6 @@ if __name__== "__main__":
(options, argv) = parser.parse_args() (options, argv) = parser.parse_args()
config = Configuration.Config() config = Configuration.Config()
# db = fpdb_db.fpdb_db()
settings = {} settings = {}
settings['minPrint'] = options.minPrint settings['minPrint'] = options.minPrint

View File

@ -27,7 +27,6 @@ from time import time, strftime
import Card import Card
import fpdb_import import fpdb_import
import Database import Database
import fpdb_db
import Filters import Filters
import Charset import Charset

View File

@ -445,6 +445,43 @@ Left-Drag to Move"
<location seat="9" x="70" y="53"> </location> <location seat="9" x="70" y="53"> </location>
</layout> </layout>
</site> </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_sites>
<supported_games> <supported_games>
@ -585,11 +622,12 @@ Left-Drag to Move"
<hhc site="PartyPoker" converter="PartyPokerToFpdb"/> <hhc site="PartyPoker" converter="PartyPokerToFpdb"/>
<hhc site="Betfair" converter="BetfairToFpdb"/> <hhc site="Betfair" converter="BetfairToFpdb"/>
<hhc site="Partouche" converter="PartoucheToFpdb"/> <hhc site="Partouche" converter="PartoucheToFpdb"/>
<hhc site="Carbon" converter="CarbonToFpdb"/>
</hhcs> </hhcs>
<supported_databases> <supported_databases>
<database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database> <!-- <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_ip="localhost" db_server="sqlite" db_name="fpdb.db3" db_user="fpdb" db_pass="fpdb"/>
</supported_databases> </supported_databases>
</FreePokerToolsConfig> </FreePokerToolsConfig>

View File

@ -36,9 +36,7 @@ import traceback
(options, argv) = Options.fpdb_options() (options, argv) = Options.fpdb_options()
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 logged. Any major error will be reported there _only_."
errorFile = open('HUD-error.txt', 'w', 0)
sys.stderr = errorFile
import thread import thread
import time import time
@ -52,6 +50,13 @@ import gobject
# FreePokerTools modules # FreePokerTools modules
import Configuration import Configuration
print "start logging"
log = Configuration.get_logger("logging.conf", config = 'hud')
log.debug("%s logger initialized." % "dud")
print "logging started"
import Database import Database
from HandHistoryConverter import getTableTitleRe from HandHistoryConverter import getTableTitleRe
# get the correct module for the current os # get the correct module for the current os
@ -72,6 +77,7 @@ class HUD_main(object):
def __init__(self, db_name = 'fpdb'): def __init__(self, db_name = 'fpdb'):
self.db_name = db_name self.db_name = db_name
self.log = log
self.config = Configuration.Config(file=options.config, dbname=options.dbname) self.config = Configuration.Config(file=options.config, dbname=options.dbname)
self.hud_dict = {} self.hud_dict = {}
self.hud_params = self.config.get_hud_ui_parameters() self.hud_params = self.config.get_hud_ui_parameters()
@ -91,6 +97,7 @@ class HUD_main(object):
self.main_window.show_all() self.main_window.show_all()
def destroy(self, *args): # call back for terminating the main eventloop def destroy(self, *args): # call back for terminating the main eventloop
self.log.info("Terminating normally.")
gtk.main_quit() gtk.main_quit()
def kill_hud(self, event, table): def kill_hud(self, event, table):
@ -198,6 +205,7 @@ class HUD_main(object):
t0 = time.time() t0 = time.time()
t1 = t2 = t3 = t4 = t5 = t6 = t0 t1 = t2 = t3 = t4 = t5 = t6 = t0
new_hand_id = string.rstrip(new_hand_id) 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 if new_hand_id == "": # blank line means quit
self.destroy() self.destroy()
break # this thread is not always killed immediately with gtk.main_quit() break # this thread is not always killed immediately with gtk.main_quit()
@ -207,9 +215,8 @@ class HUD_main(object):
try: try:
(table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \ (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) self.db_connection.get_table_info(new_hand_id)
except Exception, err: # TODO: we need to make this a much less generic Exception lulz except Exception, err:
print "db error: skipping %s" % new_hand_id self.log.error("db error: skipping %s" % new_hand_id)
sys.stderr.write("Database error: could not find hand %s.\n" % new_hand_id)
continue continue
t1 = time.time() t1 = time.time()
@ -267,7 +274,8 @@ class HUD_main(object):
# If no client window is found on the screen, complain and continue # If no client window is found on the screen, complain and continue
if type == "tour": if type == "tour":
table_name = "%s %s" % (tour_number, tab_number) 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: else:
tablewindow.max = max tablewindow.max = max
tablewindow.site = site_name tablewindow.site = site_name
@ -284,8 +292,8 @@ class HUD_main(object):
if __name__== "__main__": if __name__== "__main__":
sys.stderr.write("HUD_main starting\n") log.info("HUD_main starting")
sys.stderr.write("Using db name = %s\n" % (options.dbname)) log.info("Using db name = %s" % (options.dbname))
# start the HUD_main object # start the HUD_main object
hm = HUD_main(db_name = options.dbname) hm = HUD_main(db_name = options.dbname)

View File

@ -43,7 +43,7 @@ class Hand(object):
LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose' : 'HOSE', 'ha': 'HA'} 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"): 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): def insert(self, db):
""" Function to insert Hand into database """ Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits 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) self.stats.getStats(self)
@ -289,6 +289,24 @@ If a player has None chips he won't be added."""
c = c.replace(k,v) c = c.replace(k,v)
return c 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): def addAnte(self, player, ante):
log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante)) log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
if player is not None: 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) Bc = reduce(operator.add, self.bets[street][player], 0)
Rt = Decimal(amountTo) Rt = Decimal(amountTo)
C = Bp - Bc C = Bp - Bc
Rb = Rt - C Rb = Rt - C - Bc
self._addRaise(street, player, C, Rb, Rt) self._addRaise(street, player, C, Rb, Rt)
def _addRaise(self, street, player, C, Rb, Rt): def _addRaise(self, street, player, C, Rb, Rt):

View File

@ -26,29 +26,21 @@ from HandHistoryConverter import *
# PartyPoker HH Format # PartyPoker HH Format
class PartyPokerParseError(FpdbParseError): class FpdbParseError(FpdbParseError):
"Usage: raise PartyPokerParseError(<msg>[, hh=<hh>][, hid=<hid>])" "Usage: raise FpdbParseError(<msg>[, hh=<hh>][, hid=<hid>])"
def __init__(self, msg='', hh=None, hid=None): def __init__(self, msg='', hh=None, hid=None):
if hh is not None: return super(FpdbParseError, self).__init__(msg, hid=hid)
msg += "\n\nHand history attached below:\n" + self.wrapHh(hh)
return super(PartyPokerParseError, self).__init__(msg, hid=hid)
def wrapHh(self, hh): def wrapHh(self, hh):
return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \ return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \
{'DELIMETER': '#'*50, 'HH': hh} {'DELIMETER': '#'*50, 'HH': hh}
class PartyPoker(HandHistoryConverter): class PartyPoker(HandHistoryConverter):
############################################################
# Class Variables
sitename = "PartyPoker" sitename = "PartyPoker"
codepage = "cp1252" codepage = "cp1252"
siteId = 9 # TODO: automate; it's a class variable so shouldn't hit DB too often siteId = 9
filetype = "text" # "text" or "xml". I propose we subclass HHC to HHC_Text and HHC_XML. filetype = "text"
sym = {'USD': "\$", } sym = {'USD': "\$", }
# Static regexes # Static regexes
@ -92,9 +84,9 @@ class PartyPoker(HandHistoryConverter):
\((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct \((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct
Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton 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_SplitHands = re.compile('\x00+')
re_TailSplitHands = re.compile('(\x00+)') re_TailSplitHands = re.compile('(\x00+)')
lineSplitter = '\n' lineSplitter = '\n'
@ -131,16 +123,12 @@ class PartyPoker(HandHistoryConverter):
'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''} 'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''}
for key in ('CUR_SYM', 'CUR'): for key in ('CUR_SYM', 'CUR'):
subst[key] = re.escape(subst[key]) 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( self.re_PostSB = re.compile(
r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
self.re_PostBB = re.compile( self.re_PostBB = re.compile(
r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s\]\." % subst, r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
# NOTE: comma is used as a fraction part delimeter in re below
self.re_PostDead = re.compile( self.re_PostDead = re.compile(
r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst, r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." % subst,
re.MULTILINE) re.MULTILINE)
@ -195,8 +183,6 @@ class PartyPoker(HandHistoryConverter):
gametype dict is: gametype dict is:
{'limitType': xxx, 'base': xxx, 'category': xxx}""" {'limitType': xxx, 'base': xxx, 'category': xxx}"""
log.debug(PartyPokerParseError().wrapHh( handText ))
info = {} info = {}
m = self._getGameType(handText) m = self._getGameType(handText)
if m is None: if m is None:
@ -213,22 +199,16 @@ class PartyPoker(HandHistoryConverter):
for expectedField in ['LIMIT', 'GAME']: for expectedField in ['LIMIT', 'GAME']:
if mg[expectedField] is None: if mg[expectedField] is None:
raise PartyPokerParseError( raise FpdbParseError( "Cannot fetch field '%s'" % expectedField)
"Cannot fetch field '%s'" % expectedField,
hh = handText)
try: try:
info['limitType'] = limits[mg['LIMIT'].strip()] info['limitType'] = limits[mg['LIMIT'].strip()]
except: except:
raise PartyPokerParseError( raise FpdbParseError("Unknown limit '%s'" % mg['LIMIT'])
"Unknown limit '%s'" % mg['LIMIT'],
hh = handText)
try: try:
(info['base'], info['category']) = games[mg['GAME']] (info['base'], info['category']) = games[mg['GAME']]
except: except:
raise PartyPokerParseError( raise FpdbParseError("Unknown game type '%s'" % mg['GAME'])
"Unknown game type '%s'" % mg['GAME'],
hh = handText)
if 'TOURNO' in mg: if 'TOURNO' in mg:
info['type'] = 'tour' info['type'] = 'tour'
@ -251,23 +231,21 @@ class PartyPoker(HandHistoryConverter):
try: try:
info.update(self.re_Hid.search(hand.handText).groupdict()) info.update(self.re_Hid.search(hand.handText).groupdict())
except: except:
raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText) raise FpdbParseError("Cannot read HID for current hand")
try: try:
info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict()) info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict())
except: except:
raise PartyPokerParseError("Cannot read Handinfo for current hand", raise FpdbParseError("Cannot read Handinfo for current hand", hid = info['HID'])
hh=hand.handText, hid = info['HID'])
try: try:
info.update(self._getGameType(hand.handText).groupdict()) info.update(self._getGameType(hand.handText).groupdict())
except: except:
raise PartyPokerParseError("Cannot read GameType for current hand", raise FpdbParseError("Cannot read GameType for current hand", hid = info['HID'])
hh=hand.handText, hid = info['HID'])
# m = self.re_TotalPlayers.search(hand.handText) m = self.re_CountedSeats.search(hand.handText)
# if m: info.update(m.groupdict()) if m: info.update(m.groupdict())
# FIXME: it's dirty hack # FIXME: it's dirty hack
@ -294,6 +272,7 @@ class PartyPoker(HandHistoryConverter):
if key == 'DATETIME': if key == 'DATETIME':
#Saturday, July 25, 07:53:52 EDT 2009 #Saturday, July 25, 07:53:52 EDT 2009
#Thursday, July 30, 21:40:41 MSKS 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]) 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 # we cant use '%B' due to locale problems
months = ['January', 'February', 'March', 'April','May', 'June', months = ['January', 'February', 'March', 'April','May', 'June',
@ -317,6 +296,10 @@ class PartyPoker(HandHistoryConverter):
hand.buttonpos = info[key] hand.buttonpos = info[key]
if key == 'TOURNO': if key == 'TOURNO':
hand.tourNo = info[key] 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': if key == 'BUYIN':
# FIXME: it's dirty hack T_T # FIXME: it's dirty hack T_T
# code below assumes that tournament rake is equal to zero # code below assumes that tournament rake is equal to zero
@ -328,7 +311,7 @@ class PartyPoker(HandHistoryConverter):
if key == 'LEVEL': if key == 'LEVEL':
hand.level = info[key] hand.level = info[key]
if key == 'PLAY' and info['PLAY'] != 'Real': 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' hand.gametype['currency'] = 'play'
def readButton(self, hand): def readButton(self, hand):
@ -413,8 +396,6 @@ class PartyPoker(HandHistoryConverter):
blind = smartMin(hand.bb, playersMap[bigBlindSeat][1]) blind = smartMin(hand.bb, playersMap[bigBlindSeat][1])
hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind) hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind)
def readHeroCards(self, hand): def readHeroCards(self, hand):
# we need to grab hero's cards # we need to grab hero's cards
for street in ('PREFLOP',): for street in ('PREFLOP',):
@ -425,7 +406,6 @@ class PartyPoker(HandHistoryConverter):
newcards = renderCards(found.group('NEWCARDS')) newcards = renderCards(found.group('NEWCARDS'))
hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True)
def readAction(self, hand, street): def readAction(self, hand, street):
m = self.re_Action.finditer(hand.streets[street]) m = self.re_Action.finditer(hand.streets[street])
for action in m: for action in m:
@ -460,10 +440,9 @@ class PartyPoker(HandHistoryConverter):
elif actionType == 'checks': elif actionType == 'checks':
hand.addCheck( street, playerName ) hand.addCheck( street, playerName )
else: else:
raise PartyPokerParseError( raise FpdbParseError(
"Unimplemented readAction: '%s' '%s'" % (playerName,actionType,), "Unimplemented readAction: '%s' '%s'" % (playerName,actionType,),
hid = hand.hid, hh = hand.handText ) hid = hand.hid, )
def readShowdownActions(self, hand): def readShowdownActions(self, hand):
# all action in readShownCards # all action in readShownCards
@ -482,6 +461,17 @@ class PartyPoker(HandHistoryConverter):
hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked) 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): def ringBlinds(ringLimit):
"Returns blinds for current limit in cash games" "Returns blinds for current limit in cash games"
ringLimit = float(clearMoneyString(ringLimit)) ringLimit = float(clearMoneyString(ringLimit))

View File

@ -240,7 +240,6 @@ class PokerStars(HandHistoryConverter):
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
log.debug("readPlayerStacks") log.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText) m = self.re_PlayerInfo.finditer(hand.handText)
players = []
for a in m: for a in m:
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))

View File

@ -58,6 +58,27 @@ class Sql:
self.query['drop_table'] = """DROP TABLE IF EXISTS """ 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 # Create Settings
################################ ################################
@ -214,6 +235,7 @@ class Sql:
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id),
tableName VARCHAR(22) NOT NULL, tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL, siteHandNo BIGINT NOT NULL,
tourneyId INT UNSIGNED NOT NULL,
gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
handStart DATETIME NOT NULL, handStart DATETIME NOT NULL,
importTime DATETIME NOT NULL, importTime DATETIME NOT NULL,
@ -249,6 +271,7 @@ class Sql:
id BIGSERIAL, PRIMARY KEY (id), id BIGSERIAL, PRIMARY KEY (id),
tableName VARCHAR(22) NOT NULL, tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL, siteHandNo BIGINT NOT NULL,
tourneyId INT NOT NULL,
gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id),
handStart timestamp without time zone NOT NULL, handStart timestamp without time zone NOT NULL,
importTime timestamp without time zone NOT NULL, importTime timestamp without time zone NOT NULL,
@ -283,6 +306,7 @@ class Sql:
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
tableName TEXT(22) NOT NULL, tableName TEXT(22) NOT NULL,
siteHandNo INT NOT NULL, siteHandNo INT NOT NULL,
tourneyId INT NOT NULL,
gametypeId INT NOT NULL, gametypeId INT NOT NULL,
handStart REAL NOT NULL, handStart REAL NOT NULL,
importTime REAL NOT NULL, importTime REAL NOT NULL,
@ -3437,6 +3461,7 @@ class Sql:
tablename, tablename,
gametypeid, gametypeid,
sitehandno, sitehandno,
tourneyId,
handstart, handstart,
importtime, importtime,
seats, seats,
@ -3467,7 +3492,7 @@ class Sql:
VALUES 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, %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 ( self.query['store_hands_players'] = """INSERT INTO HandsPlayers (

View File

@ -185,7 +185,7 @@ class Tourney(object):
def old_insert_from_Hand(self, db): def old_insert_from_Hand(self, db):
""" Function to insert Hand into database """ Function to insert Hand into database
Should not commit, and do minimal selects. Callers may want to cache commits 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: # TODO:
# Players - base playerid and siteid tuple # Players - base playerid and siteid tuple
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId) sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)

View File

@ -112,7 +112,6 @@ import GuiGraphViewer
import GuiSessionViewer import GuiSessionViewer
import SQL import SQL
import Database import Database
import FpdbSQLQueries
import Configuration import Configuration
import Exceptions import Exceptions

View File

@ -16,207 +16,6 @@
#In the "official" distribution you can find the license in #In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package. #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 #end class fpdb_db

View File

@ -35,7 +35,6 @@ import gtk
# fpdb/FreePokerTools modules # fpdb/FreePokerTools modules
import fpdb_db
import Database import Database
import Configuration import Configuration
import Exceptions import Exceptions

0
pyfpdb/test_PokerStars.py Normal file → Executable file
View File

5
run_fpdb.py Executable file → Normal file
View File

@ -19,8 +19,11 @@
import os import os
import sys 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" 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 import fpdb