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

This commit is contained in:
Eric Blade 2010-01-27 13:41:04 -05:00
commit 2dd5536d98
18 changed files with 387 additions and 328 deletions

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, ))
@ -844,6 +1034,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
@ -1109,7 +1300,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')")
@ -1265,7 +1456,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
########################### ###########################
@ -1284,6 +1475,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>.*) (?! collected )?\(\$(?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>.*) (?! collected )?\(\$?(?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
@ -114,6 +112,7 @@ class Fulltilt(HandHistoryConverter):
# These regexes are for FTP only # These regexes are for FTP only
re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE) re_Mixed = re.compile(r'\s\-\s(?P<MIXED>HA|HORSE|HOSE)\s\-\s', re.VERBOSE)
re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE) re_Max = re.compile("(?P<MAX>\d+)( max)?", re.MULTILINE)
re_Collected = re.compile(" collected")
# NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports. # NB: if we ever match "Full Tilt Poker" we should also match "FullTiltPoker", which PT Stud erroneously exports.
@ -191,7 +190,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,7 +256,6 @@ 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):
if hand.gametype['type'] == "ring" : if hand.gametype['type'] == "ring" :
@ -266,7 +263,6 @@ class Fulltilt(HandHistoryConverter):
else: #if hand.gametype['type'] == "tour" else: #if hand.gametype['type'] == "tour"
m = self.re_TourneyPlayerInfo.finditer(hand.handText) m = self.re_TourneyPlayerInfo.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'))
@ -422,7 +418,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 +537,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

@ -588,8 +588,8 @@ Left-Drag to Move"
</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

@ -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)
@ -396,7 +396,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

@ -237,7 +237,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

34
run_fpdb.py Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/python
#Copyright 2008 Carl Gherardi
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
import os
import sys
# 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
if __name__ == "__main__":
me = fpdb.fpdb()
me.main()
exit()