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

This commit is contained in:
Mika Bostrom 2009-11-10 09:00:17 +02:00
commit 824a102691
14 changed files with 4026 additions and 3474 deletions

View File

@ -37,21 +37,101 @@ from xml.dom.minidom import Node
import logging, logging.config import logging, logging.config
import ConfigParser import ConfigParser
try: # local path ##############################################################################
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf")) # Functions for finding config files and setting up logging
except ConfigParser.NoSectionError: # debian package path # Also used in other modules that use logging.
logging.config.fileConfig('/usr/share/python-fpdb/logging.conf')
log = logging.getLogger("config") def get_default_config_path():
"""Returns the path where the fpdb config file _should_ be stored."""
if os.name == 'posix':
config_path = os.path.join(os.path.expanduser("~"), '.fpdb')
elif os.name == 'nt':
config_path = os.path.join(os.environ["APPDATA"], 'fpdb')
else: config_path = False
return config_path
def get_exec_path():
"""Returns the path to the fpdb.(py|exe) file we are executing"""
if hasattr(sys, "frozen"): # compiled by py2exe
return os.path.dirname(sys.executable)
else:
return os.path.dirname(sys.path[0])
def get_config(file_name, fallback = True):
"""Looks in cwd and in self.default_config_path for a config file."""
config_path = os.path.join(get_exec_path(), file_name)
if os.path.exists(config_path): # there is a file in the cwd
return config_path # so we use it
else: # no file in the cwd, look where it should be in the first place
config_path = os.path.join(get_default_config_path(), file_name)
if os.path.exists(config_path):
return config_path
# No file found
if not fallback:
return False
# OK, fall back to the .example file, should be in the start dir
if os.path.exists(file_name + ".example"):
try:
shutil.copyfile(file_name + ".example", file_name)
print "No %s found, using %s.example.\n" % (file_name, file_name)
print "A %s file has been created. You will probably have to edit it." % file_name
sys.stderr.write("No %s found, using %s.example.\n" % (file_name, file_name) )
except:
print "No %s found, cannot fall back. Exiting.\n" % file_name
sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name)
sys.exit()
return file_name
def get_logger(file_name, config = "config", fallback = False):
conf = get_config(file_name, fallback = fallback)
if conf:
try:
logging.config.fileConfig(conf)
log = logging.getLogger(config)
log.debug("%s logger initialised" % config)
return log
except:
pass
log = logging.basicConfig()
log = logging.getLogger()
log.debug("config logger initialised") log.debug("config logger initialised")
return log
def fix_tf(x, default = True): # find a logging.conf file and set up logging
# The xml parser doesn't translate "True" to True. Therefore, we never get log = get_logger("logging.conf")
# True or False from the parser only "True" or "False". So translate the
# string to the python boolean representation. ########################################################################
if x == "1" or x == 1 or string.lower(x) == "true" or string.lower(x) == "t": # application wide consts
APPLICATION_NAME_SHORT = 'fpdb'
APPLICATION_VERSION = 'xx.xx.xx'
DIR_SELF = os.path.dirname(get_exec_path())
#TODO: imo no good idea to place 'database' in parent dir
DIR_DATABASES = os.path.join(os.path.dirname(DIR_SELF), 'database')
DATABASE_TYPE_POSTGRESQL = 'postgresql'
DATABASE_TYPE_SQLITE = 'sqlite'
DATABASE_TYPE_MYSQL = 'mysql'
DATABASE_TYPES = (
DATABASE_TYPE_POSTGRESQL,
DATABASE_TYPE_SQLITE,
DATABASE_TYPE_MYSQL,
)
########################################################################
def string_to_bool(string, default=True):
"""converts a string representation of a boolean value to boolean True or False
@param string: (str) the string to convert
@param default: value to return if the string can not be converted to a boolean value
"""
string = string.lower()
if string in ('1', 'true', 't'):
return True return True
if x == "0" or x == 0 or string.lower(x) == "false" or string.lower(x) == "f": elif string in ('0', 'false', 'f'):
return False return False
return default return default
@ -106,7 +186,7 @@ class Site:
self.font = node.getAttribute("font") self.font = node.getAttribute("font")
self.font_size = node.getAttribute("font_size") self.font_size = node.getAttribute("font_size")
self.use_frames = node.getAttribute("use_frames") self.use_frames = node.getAttribute("use_frames")
self.enabled = fix_tf(node.getAttribute("enabled"), default = True) self.enabled = string_to_bool(node.getAttribute("enabled"), default=True)
self.xpad = node.getAttribute("xpad") self.xpad = node.getAttribute("xpad")
self.ypad = node.getAttribute("ypad") self.ypad = node.getAttribute("ypad")
self.layout = {} self.layout = {}
@ -201,14 +281,13 @@ class Game:
class Database: class Database:
def __init__(self, node): def __init__(self, node):
self.db_name = node.getAttribute("db_name") self.db_name = node.getAttribute("db_name")
self.db_server = node.getAttribute("db_server") self.db_server = node.getAttribute("db_server").lower()
self.db_ip = node.getAttribute("db_ip") self.db_ip = node.getAttribute("db_ip")
self.db_user = node.getAttribute("db_user") self.db_user = node.getAttribute("db_user")
self.db_type = node.getAttribute("db_type")
self.db_pass = node.getAttribute("db_pass") self.db_pass = node.getAttribute("db_pass")
self.db_selected = fix_tf(node.getAttribute("default"),"False") self.db_selected = string_to_bool(node.getAttribute("default"), default=False)
log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_type:'%(type)s' db_pass (not logged) selected:'%(sel)s'" \ log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_pass (not logged) selected:'%(sel)s'" \
% { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'type':self.db_type, 'sel':self.db_selected} ) % { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'sel':self.db_selected} )
def __str__(self): def __str__(self):
temp = 'Database = ' + self.db_name + '\n' temp = 'Database = ' + self.db_name + '\n'
@ -270,8 +349,8 @@ class Import:
self.interval = node.getAttribute("interval") self.interval = node.getAttribute("interval")
self.callFpdbHud = node.getAttribute("callFpdbHud") self.callFpdbHud = node.getAttribute("callFpdbHud")
self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.hhArchiveBase = node.getAttribute("hhArchiveBase")
self.saveActions = fix_tf(node.getAttribute("saveActions"), True) self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True)
self.fastStoreHudCache = fix_tf(node.getAttribute("fastStoreHudCache"), False) self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False)
def __str__(self): def __str__(self):
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \ return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \
@ -282,14 +361,14 @@ class HudUI:
self.node = node self.node = node
self.label = node.getAttribute('label') self.label = node.getAttribute('label')
# #
self.aggregate_ring = fix_tf(node.getAttribute('aggregate_ring_game_stats')) self.aggregate_ring = string_to_bool(node.getAttribute('aggregate_ring_game_stats'))
self.aggregate_tour = fix_tf(node.getAttribute('aggregate_tourney_stats')) self.aggregate_tour = string_to_bool(node.getAttribute('aggregate_tourney_stats'))
self.hud_style = node.getAttribute('stat_aggregation_range') self.hud_style = node.getAttribute('stat_aggregation_range')
self.hud_days = node.getAttribute('aggregation_days') self.hud_days = node.getAttribute('aggregation_days')
self.agg_bb_mult = node.getAttribute('aggregation_level_multiplier') self.agg_bb_mult = node.getAttribute('aggregation_level_multiplier')
# #
self.h_aggregate_ring = fix_tf(node.getAttribute('aggregate_hero_ring_game_stats')) self.h_aggregate_ring = string_to_bool(node.getAttribute('aggregate_hero_ring_game_stats'))
self.h_aggregate_tour = fix_tf(node.getAttribute('aggregate_hero_tourney_stats')) self.h_aggregate_tour = string_to_bool(node.getAttribute('aggregate_hero_tourney_stats'))
self.h_hud_style = node.getAttribute('hero_stat_aggregation_range') self.h_hud_style = node.getAttribute('hero_stat_aggregation_range')
self.h_hud_days = node.getAttribute('hero_aggregation_days') self.h_hud_days = node.getAttribute('hero_aggregation_days')
self.h_agg_bb_mult = node.getAttribute('hero_aggregation_level_multiplier') self.h_agg_bb_mult = node.getAttribute('hero_aggregation_level_multiplier')
@ -301,9 +380,9 @@ class HudUI:
class Tv: class Tv:
def __init__(self, node): def __init__(self, node):
self.combinedStealFold = node.getAttribute("combinedStealFold") self.combinedStealFold = string_to_bool(node.getAttribute("combinedStealFold"), default=True)
self.combined2B3B = node.getAttribute("combined2B3B") self.combined2B3B = string_to_bool(node.getAttribute("combined2B3B"), default=True)
self.combinedPostflop = node.getAttribute("combinedPostflop") self.combinedPostflop = string_to_bool(node.getAttribute("combinedPostflop"), default=True)
def __str__(self): def __str__(self):
return (" combinedStealFold = %s\n combined2B3B = %s\n combinedPostflop = %s\n" % return (" combinedStealFold = %s\n combined2B3B = %s\n combinedPostflop = %s\n" %
@ -315,7 +394,7 @@ class Config:
# "file" is a path to an xml file with the fpdb/HUD configuration # "file" is a path to an xml file with the fpdb/HUD configuration
# we check the existence of "file" and try to recover if it doesn't exist # we check the existence of "file" and try to recover if it doesn't exist
self.default_config_path = self.get_default_config_path() # self.default_config_path = self.get_default_config_path()
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):
@ -323,28 +402,12 @@ class Config:
sys.stderr.write("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: # configuration file path not passed or invalid file = get_config("HUD_config.xml")
file = self.find_config() #Look for a config file in the normal places
if file is None: # no config file in the normal places
file = self.find_example_config() #Look for an example file to edit
if file is None: # that didn't work either, just die
print "No HUD_config_xml found after looking in current directory and "+self.default_config_path+"\nExiting"
sys.stderr.write("No HUD_config_xml found after looking in current directory and "+self.default_config_path+"\nExiting")
print "press enter to continue"
sys.stdin.readline()
sys.exit()
# 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
# sc 2009/10/04 Example already copied to main filename, is this ok?
log.info("Reading configuration file %s" % file) log.info("Reading configuration file %s" % file)
if os.sep in file:
print "\nReading configuration file %s\n" % file print "\nReading configuration file %s\n" % file
else:
print "\nReading configuration file %s" % file
print "in %s\n" % os.getcwd()
try: try:
doc = xml.dom.minidom.parse(file) doc = xml.dom.minidom.parse(file)
except: except:
@ -358,10 +421,13 @@ class Config:
self.file = file self.file = file
self.supported_sites = {} self.supported_sites = {}
self.supported_games = {} self.supported_games = {}
self.supported_databases = {} self.supported_databases = {} # databaseName --> Database instance
self.aux_windows = {} self.aux_windows = {}
self.hhcs = {} self.hhcs = {}
self.popup_windows = {} self.popup_windows = {}
self.db_selected = None # database the user would like to use
self.tv = None
# s_sites = doc.getElementsByTagName("supported_sites") # s_sites = doc.getElementsByTagName("supported_sites")
for site_node in doc.getElementsByTagName("site"): for site_node in doc.getElementsByTagName("site"):
@ -373,29 +439,26 @@ class Config:
game = Game(node = game_node) game = Game(node = game_node)
self.supported_games[game.game_name] = game self.supported_games[game.game_name] = game
# parse databases defined by user in the <supported_databases> section
# the user may select the actual database to use via commandline or by setting the selected="bool"
# attribute of the tag. if no database is explicitely selected, we use the first one we come across
# s_dbs = doc.getElementsByTagName("supported_databases") # s_dbs = doc.getElementsByTagName("supported_databases")
# select database from those defined in config by: #TODO: do we want to take all <database> tags or all <database> tags contained in <supported_databases>
# 1) command line option # ..this may break stuff for some users. so leave it unchanged for now untill there is a decission
# or 2) selected="True" in config element
# or 3) just choose the first we come across
for db_node in doc.getElementsByTagName("database"): for db_node in doc.getElementsByTagName("database"):
try:
db = Database(node=db_node) db = Database(node=db_node)
except:
raise FpdbError("Unable to create database object")
else:
if db.db_name in self.supported_databases: if db.db_name in self.supported_databases:
raise FpdbError("Database names must be unique") raise ValueError("Database names must be unique")
# If there is only one Database node, or none are marked if self.db_selected is None or db.db_selected:
# default, use first
if not self.supported_databases:
self.db_selected = db.db_name self.db_selected = db.db_name
self.supported_databases[db.db_name] = db self.supported_databases[db.db_name] = db
if db.db_selected: #TODO: if the user may passes '' (empty string) as database name via command line, his choice is ignored
self.db_selected = db.db_name # ..when we parse the xml we allow for ''. there has to be a decission if to allow '' or not
if dbname and dbname in self.supported_databases: if dbname and dbname in self.supported_databases:
self.db_selected = dbname self.db_selected = dbname
#NOTE: fpdb can not handle the case when no database is defined in xml, so we throw an exception for now
if self.db_selected is None:
raise ValueError('There must be at least one database defined')
# s_dbs = doc.getElementsByTagName("mucked_windows") # s_dbs = doc.getElementsByTagName("mucked_windows")
for aw_node in doc.getElementsByTagName("aw"): for aw_node in doc.getElementsByTagName("aw"):
@ -421,8 +484,7 @@ class Config:
self.ui = hui self.ui = hui
for tv_node in doc.getElementsByTagName("tv"): for tv_node in doc.getElementsByTagName("tv"):
tv = Tv(node = tv_node) self.tv = Tv(node = tv_node)
self.tv = tv
db = self.get_db_parameters() db = self.get_db_parameters()
if db['db-password'] == 'YOUR MYSQL PASSWORD': if db['db-password'] == 'YOUR MYSQL PASSWORD':
@ -441,28 +503,6 @@ class Config:
def set_hhArchiveBase(self, path): def set_hhArchiveBase(self, path):
self.imp.node.setAttribute("hhArchiveBase", path) self.imp.node.setAttribute("hhArchiveBase", path)
def find_config(self):
"""Looks in cwd and in self.default_config_path for a config file."""
if os.path.exists('HUD_config.xml'): # there is a HUD_config in the cwd
file = 'HUD_config.xml' # so we use it
else: # no HUD_config in the cwd, look where it should be in the first place
config_path = os.path.join(self.default_config_path, 'HUD_config.xml')
if os.path.exists(config_path):
file = config_path
else:
file = None
return file
def get_default_config_path(self):
"""Returns the path where the fpdb config file _should_ be stored."""
if os.name == 'posix':
config_path = os.path.join(os.path.expanduser("~"), '.fpdb')
elif os.name == 'nt':
config_path = os.path.join(os.environ["APPDATA"], 'fpdb')
else: config_path = None
return config_path
def find_default_conf(self): def find_default_conf(self):
if os.name == 'posix': if os.name == 'posix':
config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'default.conf') config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'default.conf')
@ -476,30 +516,6 @@ class Config:
file = None file = None
return file return file
def read_default_conf(self, file):
parms = {}
with open(file, "r") as fh:
for line in fh:
line = string.strip(line)
(key, value) = line.split('=')
parms[key] = value
return parms
def find_example_config(self):
if os.path.exists('HUD_config.xml.example'): # there is a HUD_config in the cwd
file = 'HUD_config.xml' # so we use it
try:
shutil.copyfile(file+'.example', file)
except:
file = ''
print "No HUD_config.xml found, using HUD_config.xml.example.\n", \
"A HUD_config.xml has been created. You will probably have to edit it."
sys.stderr.write("No HUD_config.xml found, using HUD_config.xml.example.\n" + \
"A HUD_config.xml has been created. You will probably have to edit it.")
else:
file = None
return file
def get_site_node(self, site): def get_site_node(self, site):
for site_node in self.doc.getElementsByTagName("site"): for site_node in self.doc.getElementsByTagName("site"):
if site_node.getAttribute("site_name") == site: if site_node.getAttribute("site_name") == site:
@ -539,7 +555,7 @@ class Config:
self.doc.writexml(f) self.doc.writexml(f)
else: else:
shutil.move(self.file, self.file+".backup") shutil.move(self.file, self.file+".backup")
with open(self.file, 'w') as f: with open(file, 'w') as f:
self.doc.writexml(f) self.doc.writexml(f)
def edit_layout(self, site_name, max, width = None, height = None, def edit_layout(self, site_name, max, width = None, height = None,
@ -571,6 +587,13 @@ class Config:
else: else:
self.aux_windows[aux_name].layout[max].location[i] = ( locations[i][0], locations[i][1] ) self.aux_windows[aux_name].layout[max].location[i] = ( locations[i][0], locations[i][1] )
#NOTE: we got a nice Database class, so why map it again here?
# user input validation should be done when initializing the Database class. this allows to give appropriate feddback when something goes wrong
# try ..except is evil here. it swallows all kinds of errors. dont do this
# naming database types 2, 3, 4 on the fly is no good idea. i see this all over the code. better use some globally defined consts (see DATABASE_TYPE_*)
# i would like to drop this method entirely and replace it by get_selected_database() or better get_active_database(), returning one of our Database instances
# thus we can drop self.db_selected (holding database name) entirely and replace it with self._active_database = Database, avoiding to define the same
# thing multiple times
def get_db_parameters(self): def get_db_parameters(self):
db = {} db = {}
name = self.db_selected name = self.db_selected
@ -590,20 +613,18 @@ class Config:
try: db['db-server'] = self.supported_databases[name].db_server try: db['db-server'] = self.supported_databases[name].db_server
except: pass except: pass
try: db['db-type'] = self.supported_databases[name].db_type if self.supported_databases[name].db_server== DATABASE_TYPE_MYSQL:
except: pass
if string.lower(self.supported_databases[name].db_server) == 'mysql':
db['db-backend'] = 2 db['db-backend'] = 2
elif string.lower(self.supported_databases[name].db_server) == 'postgresql': elif self.supported_databases[name].db_server== DATABASE_TYPE_POSTGRESQL:
db['db-backend'] = 3 db['db-backend'] = 3
elif string.lower(self.supported_databases[name].db_server) == 'sqlite': elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE:
db['db-backend'] = 4 db['db-backend'] = 4
else: db['db-backend'] = None # this is big trouble else:
raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server)
return db return db
def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None, def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None,
db_pass = None, db_server = None, db_type = None): db_pass = None, db_server = None):
db_node = self.get_db_node(db_name) db_node = self.get_db_node(db_name)
if db_node != None: if db_node != None:
if db_ip is not None: db_node.setAttribute("db_ip", db_ip) if db_ip is not None: db_node.setAttribute("db_ip", db_ip)
@ -627,16 +648,13 @@ class Config:
return None return None
def get_tv_parameters(self): def get_tv_parameters(self):
tv = {} if self.tv is not None:
try: tv['combinedStealFold'] = self.tv.combinedStealFold return {
except: tv['combinedStealFold'] = True 'combinedStealFold': self.tv.combinedStealFold,
'combined2B3B': self.tv.combined2B3B,
try: tv['combined2B3B'] = self.tv.combined2B3B 'combinedPostflop': self.tv.combinedPostflop
except: tv['combined2B3B'] = True }
return {}
try: tv['combinedPostflop'] = self.tv.combinedPostflop
except: tv['combinedPostflop'] = True
return tv
# Allow to change the menu appearance # Allow to change the menu appearance
def get_hud_ui_parameters(self): def get_hud_ui_parameters(self):
@ -735,29 +753,27 @@ class Config:
return colors return colors
def get_default_font(self, site='PokerStars'): def get_default_font(self, site='PokerStars'):
(font, font_size) = ("Sans", "8")
if site not in self.supported_sites:
return ("Sans", "8")
if self.supported_sites[site].font == "":
font = "Sans" font = "Sans"
else:
font = self.supported_sites[site].font
if self.supported_sites[site].font_size == "":
font_size = "8" font_size = "8"
else: site = self.supported_sites.get(site, None)
font_size = self.supported_sites[site].font_size if site is not None:
return (font, font_size) if site.font:
font = site.font
if site.font_size:
font_size = site.font_size
return font, font_size
def get_locations(self, site = "PokerStars", max = "8"): def get_locations(self, site_name="PokerStars", max=8):
site = self.supported_sites.get(site_name, None)
try: if site is not None:
locations = self.supported_sites[site].layout[max].location location = site.layout.get(max, None)
except: if location is not None:
locations = ( ( 0, 0), (684, 61), (689, 239), (692, 346), return location.location
return (
( 0, 0), (684, 61), (689, 239), (692, 346),
(586, 393), (421, 440), (267, 440), ( 0, 361), (586, 393), (421, 440), (267, 440), ( 0, 361),
( 0, 280), (121, 280), ( 46, 30) ) ( 0, 280), (121, 280), ( 46, 30)
return locations )
def get_aux_locations(self, aux = "mucked", max = "9"): def get_aux_locations(self, aux = "mucked", max = "9"):
@ -771,12 +787,10 @@ class Config:
def get_supported_sites(self, all=False): def get_supported_sites(self, all=False):
"""Returns the list of supported sites.""" """Returns the list of supported sites."""
the_sites = [] if all:
for site in self.supported_sites.keys(): return self.supported_sites.keys()
params = self.get_site_parameters(site) else:
if all or params['enabled']: return [site_name for (site_name, site) in self.supported_sites.items() if site.enabled]
the_sites.append(site)
return the_sites
def get_site_parameters(self, site): def get_site_parameters(self, site):
"""Returns a dict of the site parameters for the specified site""" """Returns a dict of the site parameters for the specified site"""
@ -824,10 +838,7 @@ class Config:
def get_aux_windows(self): def get_aux_windows(self):
"""Gets the list of mucked window formats in the configuration.""" """Gets the list of mucked window formats in the configuration."""
mw = [] return self.aux_windows.keys()
for w in self.aux_windows.keys():
mw.append(w)
return mw
def get_aux_parameters(self, name): def get_aux_parameters(self, name):
"""Gets a dict of mucked window parameters from the named mw.""" """Gets a dict of mucked window parameters from the named mw."""

View File

@ -45,16 +45,7 @@ import Card
import Tourney import Tourney
from Exceptions import * from Exceptions import *
import logging, logging.config log = Configuration.get_logger("logging.conf")
import ConfigParser
try: # local path
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
except ConfigParser.NoSectionError: # debian package path
logging.config.fileConfig('/usr/share/python-fpdb/logging.conf')
log = logging.getLogger('db')
class Database: class Database:
@ -205,7 +196,7 @@ class Database:
# 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:
self.sql = SQL.Sql(type = self.type, db_server = self.db_server) self.sql = SQL.Sql(db_server = self.db_server)
else: else:
self.sql = sql self.sql = sql
@ -249,7 +240,6 @@ class Database:
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()
self.type = db_params['db-type']
self.backend = db_params['db-backend'] self.backend = db_params['db-backend']
self.db_server = db_params['db-server'] self.db_server = db_params['db-server']
self.database = db_params['db-databaseName'] self.database = db_params['db-databaseName']
@ -1394,6 +1384,12 @@ class Database:
pids[p], pids[p],
pdata[p]['startCash'], pdata[p]['startCash'],
pdata[p]['seatNo'], pdata[p]['seatNo'],
pdata[p]['winnings'],
pdata[p]['street0VPI'],
pdata[p]['street1Seen'],
pdata[p]['street2Seen'],
pdata[p]['street3Seen'],
pdata[p]['street4Seen'],
pdata[p]['street0Aggr'], pdata[p]['street0Aggr'],
pdata[p]['street1Aggr'], pdata[p]['street1Aggr'],
pdata[p]['street2Aggr'], pdata[p]['street2Aggr'],
@ -1406,6 +1402,12 @@ class Database:
playerId, playerId,
startCash, startCash,
seatNo, seatNo,
winnings,
street0VPI,
street1Seen,
street2Seen,
street3Seen,
street4Seen,
street0Aggr, street0Aggr,
street1Aggr, street1Aggr,
street2Aggr, street2Aggr,
@ -1414,7 +1416,8 @@ class Database:
) )
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
)""" )"""
# position, # position,
@ -1424,16 +1427,10 @@ class Database:
# card3, # card3,
# card4, # card4,
# startCards, # startCards,
# winnings,
# rake, # rake,
# totalProfit, # totalProfit,
# street0VPI,
# street0_3BChance, # street0_3BChance,
# street0_3BDone, # street0_3BDone,
# street1Seen,
# street2Seen,
# street3Seen,
# street4Seen,
# sawShowdown, # sawShowdown,
# otherRaisedStreet1, # otherRaisedStreet1,
# otherRaisedStreet2, # otherRaisedStreet2,
@ -2684,13 +2681,11 @@ class HandToWrite:
if __name__=="__main__": if __name__=="__main__":
c = Configuration.Config() c = Configuration.Config()
db_connection = Database(c, 'fpdb', 'holdem') # mysql fpdb holdem db_connection = Database(c) # mysql fpdb holdem
# db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem # db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem
# db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz # db_connection = Database(c, 'PTrackSv2', 'razz') # mysql razz
# db_connection = Database(c, 'ptracks', 'razz') # postgres # db_connection = Database(c, 'ptracks', 'razz') # postgres
print "database connection object = ", db_connection.connection print "database connection object = ", db_connection.connection
print "database type = ", db_connection.type
db_connection.recreate_tables() db_connection.recreate_tables()
h = db_connection.get_last_hand() h = db_connection.get_last_hand()
@ -2704,18 +2699,12 @@ if __name__=="__main__":
for p in stat_dict.keys(): for p in stat_dict.keys():
print p, " ", stat_dict[p] print p, " ", stat_dict[p]
#print "nutOmatics stats:"
#stat_dict = db_connection.get_stats_from_hand(h, "ring")
#for p in stat_dict.keys():
# print p, " ", stat_dict[p]
print "cards =", db_connection.get_cards(u'1') print "cards =", db_connection.get_cards(u'1')
db_connection.close_connection db_connection.close_connection
print "press enter to continue" print "press enter to continue"
sys.stdin.readline() sys.stdin.readline()
#Code borrowed from http://push.cx/2008/caching-dictionaries-in-python-vs-ruby #Code borrowed from http://push.cx/2008/caching-dictionaries-in-python-vs-ruby
class LambdaDict(dict): class LambdaDict(dict):
def __init__(self, l): def __init__(self, l):

View File

@ -1,28 +1,85 @@
"""Database manager
@todo: (gtk) how to validate user input in gtk.Dialog? as soon as the user clicks ok the dialog is dead. we use a while loop as workaround. not nice
@todo: (fpdb) we need the application name 'fpdb' from somewhere to put it in dialog titles
@todo: (fpdb) config object should be initialized globally and accessible from all modules via Configuration.py
@todo: (all dialogs) save/restore size and pos
@todo: (WidgetDatabaseManager) give database status meaningful colors
@todo: (WidgetDatabaseManager) implement database purging
@todo: (WidgetDatabaseManager) implement database export
@todo: (WidgetDatabaseManager) what to do on database doubleclick?
@todo: (WidgetDatabaseManager) context menu for database tree
@todo: (WidgetDatabaseManager) initializing/validating databases may take a while. how to give feedback?
"""
import os import os
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import gobject
#******************************************************************************************************* #*******************************************************************************************************
class DatabaseManager(object): class DatabaseManager(gobject.GObject):
DatabaseTypes = {} DatabaseTypes = {}
@classmethod @classmethod
def from_fpdb(klass, data, defaultDatabaseType=None): def from_fpdb(klass, data, defaultDatabaseType=None):
#TODO: parse whatever data is
#TODO: sort out unsupported databases passed by user and log
databases = (
DatabaseTypeSqLite(name='myDb'),
DatabaseTypeSqLite(name='myDb2'),
) #NOTE: if no databases are present in config fpdb fails with
# Traceback (most recent call last):
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/DatabaseManager.py", line 783, in <module>
# databaseManager = DatabaseManager.from_fpdb('', defaultDatabaseType=DatabaseTypeSqLite)
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/DatabaseManager.py", line 36, in from_fpdb
# config = Configuration.Config(file=options.config, dbname=options.dbname)
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/Configuration.py", line 436, in __init__
# db = self.get_db_parameters()
# File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/Configuration.py", line 583, in get_db_parameters
# name = self.db_selected
# AttributeError: Config instance has no attribute 'db_selected'
import sys
import Options
import Configuration
#NOTE: fpdb should perform this globally
(options, sys.argv) = Options.fpdb_options()
config = Configuration.Config(file=options.config, dbname=options.dbname)
#TODO: handle no database present
defaultDatabaseName = config.get_db_parameters().get('db-databaseName', None)
#TODO: fpdb stores databases in no particular order. this has to be fixed somehow
databases = []
for name, fpdbDatabase in config.supported_databases.items():
databaseKlass = klass.DatabaseTypes.get(fpdbDatabase.db_server, None)
#NOTE: Config does not seem to validate user input, so anything may end up here
if databaseKlass is None:
raise ValueError('Unknown databasetype: %s' % fpdbDatabase.db_server)
database = databaseKlass()
if database.Type == 'sqlite':
database.name = fpdbDatabase.db_name
database.file = fpdbDatabase.db_server
else:
database.name = fpdbDatabase.db_name
database.host = fpdbDatabase.db_server
#NOTE: fpdbDatabase.db_ip is no is a string
database.port = int(fpdbDatabase.db_ip)
database.user = fpdbDatabase.db_user
database.password = fpdbDatabase.db_pass
databases.append(database)
return klass(databases=databases, defaultDatabaseType=defaultDatabaseType) return klass(databases=databases, defaultDatabaseType=defaultDatabaseType)
def to_fpdb(self):
pass
def __init__(self, databases=None, defaultDatabaseType=None): def __init__(self, databases=None, defaultDatabaseType=None):
gobject.GObject.__init__(self)
self._defaultDatabaseType = defaultDatabaseType self._defaultDatabaseType = defaultDatabaseType
self._databases = [] if databases is None else list(databases) self._databases = [] if databases is None else list(databases)
self._activeDatabase = None
def __iter__(self): def __iter__(self):
return iter(self._databases) return iter(self._databases)
def set_default_database_type(self, databaseType): def set_default_database_type(self, databaseType):
@ -31,7 +88,7 @@ class DatabaseManager(object):
return self._defaultDatabaseType return self._defaultDatabaseType
def database_from_id(self, idDatabase): def database_from_id(self, idDatabase):
for database in self._databases: for database in self._databases:
if idDatabase == id(database): if idDatabase == self.database_id(database):
return database return database
def database_id(self, database): def database_id(self, database):
return id(database) return id(database)
@ -41,8 +98,26 @@ class DatabaseManager(object):
self._databases.append(database) self._databases.append(database)
def remove_database(self, database): def remove_database(self, database):
self._databases.remove(database) self._databases.remove(database)
def init_database(self, database):
pass def activate_database(self, database):
if self._activeDatabase is not None:
self._activeDatabase.status = self._activeDatabase.StatusInactive
#TODO: finalize database
self.emit('database-deactivated', self.database_id(self._activeDatabase) )
database.status = database.StatusActive
#TODO: activate database
self._activeDatabase = database
self.emit('database-activated', self.database_id(database) )
def active_database(self):
return self._activeDatabase
# register DatabaseManager signals
gobject.type_register(DatabaseManager)
gobject.signal_new('database-activated', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, ))
gobject.signal_new('database-deactivated', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, ))
gobject.signal_new('database-error', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, ))
class DatabaseTypeMeta(type): class DatabaseTypeMeta(type):
def __new__(klass, name, bases, kws): def __new__(klass, name, bases, kws):
@ -56,10 +131,25 @@ class DatabaseTypeMeta(type):
class DatabaseTypeBase(object): class DatabaseTypeBase(object):
__metaclass__ = DatabaseTypeMeta __metaclass__ = DatabaseTypeMeta
Type = None Type = None
Params = () StatusActive = 'active'
StatusInactive = 'inactive'
StatusError = 'error' #TODO: not implemented
#TODO: not happy with returning error string. just being too lazy to impl dozens of error codes for later translation
def init_new_database(self):
"""initializes a new empty database
@return: (str) error if something goes wrong, None otherwise
"""
raise NotImplementedError()
def validate_database(self):
"""checks if the database is valid
@return: (str) error if something goes wrong, None otherwise
"""
raise NotImplementedError()
class DatabaseTypePostgres(DatabaseTypeBase): class DatabaseTypePostgres(DatabaseTypeBase):
Type = 'postgres' Type = 'postgresql'
@classmethod @classmethod
def display_name(klass): def display_name(klass):
return 'Postgres' return 'Postgres'
@ -70,6 +160,15 @@ class DatabaseTypePostgres(DatabaseTypeBase):
self.user = user self.user = user
self.password = password self.password = password
self.database = database self.database = database
self.status = self.StatusInactive
#TODO: implement
def init_new_database(self):
pass
#TODO: implement
def validate_database(self):
pass
class DatabaseTypeMysql(DatabaseTypeBase): class DatabaseTypeMysql(DatabaseTypeBase):
Type = 'mysql' Type = 'mysql'
@ -83,6 +182,16 @@ class DatabaseTypeMysql(DatabaseTypeBase):
self.user = user self.user = user
self.password = password self.password = password
self.database = database self.database = database
self.status = self.StatusInactive
#TODO: implement
def init_new_database(self):
pass
#TODO: implement
def validate_database(self):
pass
class DatabaseTypeSqLite(DatabaseTypeBase): class DatabaseTypeSqLite(DatabaseTypeBase):
Type = 'sqlite' Type = 'sqlite'
@ -92,7 +201,26 @@ class DatabaseTypeSqLite(DatabaseTypeBase):
def __init__(self, name='', host='', file='', database='fpdb'): def __init__(self, name='', host='', file='', database='fpdb'):
self.name = name self.name = name
self.file = file self.file = file
self.database = database self.status = self.StatusInactive
def init_new_database(self):
# make shure all attrs are specified
if not self.file:
return 'no database file specified'
# create file if necessary (this will truncate file if it exists)
try:
open(self.file, 'w').close()
except IOError:
return 'can not write file'
#TODO: init tables (...)
def validate_database(self):
pass
#TODO: check if tables (...) exist
#TODO: how do we want to handle unsupported database types? #TODO: how do we want to handle unsupported database types?
# ..uncomment to remove unsupported database types # ..uncomment to remove unsupported database types
@ -104,6 +232,20 @@ class DatabaseTypeSqLite(DatabaseTypeBase):
#except ImportError: del DatabaseManager.DatabaseTypes['sqlite'] #except ImportError: del DatabaseManager.DatabaseTypes['sqlite']
#*************************************************************************************************************************** #***************************************************************************************************************************
#TODO: there is no title (on linux), wtf?
def DialogError(parent=None, msg=''):
dlg = gtk.MessageDialog(
parent=parent,
flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_OK,
message_format=msg,
)
dlg.run()
dlg.destroy()
return None
#TODO: derrive from gtk.VBox? #TODO: derrive from gtk.VBox?
class WidgetDatabaseProperties(gtk.VBox): class WidgetDatabaseProperties(gtk.VBox):
@ -158,7 +300,7 @@ class WidgetDatabaseProperties(gtk.VBox):
dlg.destroy() dlg.destroy()
#TODO: bit ugly this thingy. try to find a better way to map database attrs to gtk widgets
class FieldWidget(object): class FieldWidget(object):
def __init__(self, text='', attrDatabase='', widget=None, attrGet=None, attrSet=None, defaultValue=None, canEdit=False, tooltip=''): def __init__(self, text='', attrDatabase='', widget=None, attrGet=None, attrSet=None, defaultValue=None, canEdit=False, tooltip=''):
""" """
@ -212,16 +354,6 @@ class WidgetDatabaseProperties(gtk.VBox):
canEdit=True, canEdit=True,
tooltip='Any name you like to name the database ' tooltip='Any name you like to name the database '
), ),
self.FieldWidget(
text='Db:',
attrDatabase='database',
widget=gtk.Entry(),
defaultValue='',
attrGet='get_text',
attrSet='set_text',
canEdit=False,
tooltip='Name of the database to create'
),
self.FieldWidget( self.FieldWidget(
text='File:', text='File:',
attrDatabase='file', attrDatabase='file',
@ -272,6 +404,16 @@ class WidgetDatabaseProperties(gtk.VBox):
canEdit=False, canEdit=False,
tooltip='Password used to login to the host' tooltip='Password used to login to the host'
), ),
self.FieldWidget(
text='Db:',
attrDatabase='database',
widget=gtk.Entry(),
defaultValue='',
attrGet='get_text',
attrSet='set_text',
canEdit=False,
tooltip='Name of the database'
),
) )
# setup database type combo # setup database type combo
@ -372,11 +514,22 @@ class DialogDatabaseProperties(gtk.Dialog):
#TODO: derrive from gtk.VBox? #TODO: derrive from gtk.VBox?
# ..is there a way to derrive from gtk.Widget or similar? this would make parentWidget kw obsolete # ..is there a way to derrive from gtk.Widget or similar? this would make parentWidget kw obsolete
class WidgetDatabaseManager(gtk.VBox): class WidgetDatabaseManager(gtk.VBox):
"""
"""
def __init__(self, databaseManager, parentWidget=None): def __init__(self, databaseManager, parentWidget=None):
gtk.VBox.__init__(self) gtk.VBox.__init__(self)
self.databaseManager = databaseManager
self.parentWidget = parentWidget self.parentWidget = parentWidget
self.databaseManager = databaseManager
self.databaseManager.connect('database-activated', self.on_database_manager_database_activated)
self.databaseManager.connect('database-deactivated', self.on_database_manager_database_deactivated)
self.databaseStatusNames = {
DatabaseTypeBase.StatusActive: 'Active',
DatabaseTypeBase.StatusInactive: 'Inactive',
DatabaseTypeBase.StatusError: 'Error',
}
#TODO: dono how to make word wrap work as expected #TODO: dono how to make word wrap work as expected
self.labelInfo = gtk.Label('database management') self.labelInfo = gtk.Label('database management')
@ -389,6 +542,10 @@ class WidgetDatabaseManager(gtk.VBox):
#TODO: bit messy the distinction New/Add/Edit. we'd have to pass three flags to DialogDatabaseProperties #TODO: bit messy the distinction New/Add/Edit. we'd have to pass three flags to DialogDatabaseProperties
# to handle this. maybe drop Edit (is just a Remove + Add), to keep things simple # to handle this. maybe drop Edit (is just a Remove + Add), to keep things simple
self.buttonDatabaseActivate = gtk.Button("Activate")
self.buttonDatabaseActivate.set_tooltip_text('activates the database')
self.buttonDatabaseActivate.connect('clicked', self.on_button_database_activate_clicked)
self.buttonDatabaseActivate.set_sensitive(False)
self.buttonDatabaseNew = gtk.Button("New..") self.buttonDatabaseNew = gtk.Button("New..")
self.buttonDatabaseNew.set_tooltip_text('creates a new database') self.buttonDatabaseNew.set_tooltip_text('creates a new database')
self.buttonDatabaseNew.connect('clicked', self.on_button_database_new_clicked) self.buttonDatabaseNew.connect('clicked', self.on_button_database_new_clicked)
@ -402,31 +559,30 @@ class WidgetDatabaseManager(gtk.VBox):
self.buttonDatabaseRemove = gtk.Button("Remove") self.buttonDatabaseRemove = gtk.Button("Remove")
self.buttonDatabaseRemove.set_tooltip_text('removes the database from the list') self.buttonDatabaseRemove.set_tooltip_text('removes the database from the list')
self.buttonDatabaseRemove.set_sensitive(False) self.buttonDatabaseRemove.set_sensitive(False)
self.buttonDatabaseRemove.connect('clicked', self.on_button_database_remove_clicked)
#TODO: i dont think we should do any real database management here. maybe drop it #TODO: i dont think we should do any real database management here. maybe drop it
self.buttonDatabaseDelete = gtk.Button("Delete") #self.buttonDatabaseDelete = gtk.Button("Delete")
self.buttonDatabaseDelete.set_tooltip_text('removes the database from the list and deletes it') #self.buttonDatabaseDelete.set_tooltip_text('removes the database from the list and deletes it')
self.buttonDatabaseDelete.set_sensitive(False) #self.buttonDatabaseDelete.set_sensitive(False)
# init database tree # init database tree
self.treeDatabases = gtk.TreeView() self.treeDatabases = gtk.TreeView()
self.treeDatabaseColumns = ( #NOTE: column names starting with '_' will be hidden treeDatabaseColumns = ( # name, displayName, dataType
'Name', ('name', 'Name', str),
'Status', ('status', 'Status', str),
'Type', ('type', 'Type', str),
'_id', ('_id', '', int),
) )
self.treeDatabaseColumns = {} # name --> index
store = gtk.ListStore(str, str, str, int) store = gtk.ListStore( *[i[2] for i in treeDatabaseColumns] )
self.treeDatabases.set_model(store) self.treeDatabases.set_model(store)
columns = ('Name', 'Status', 'Type', '_id') for i, (name, displayName, dataType) in enumerate(treeDatabaseColumns):
for i, column in enumerate(columns): col = gtk.TreeViewColumn(displayName, gtk.CellRendererText(), text=i)
col = gtk.TreeViewColumn(column, gtk.CellRendererText(), text=i)
self.treeDatabases.append_column(col) self.treeDatabases.append_column(col)
if column.startswith('_'): if name.startswith('_'):
col.set_visible(False) col.set_visible(False)
self.treeDatabaseColumns[name] = i
self.treeDatabaseColumns = dict([(name, i) for (i, name) in enumerate(self.treeDatabaseColumns)])
self.treeDatabases.get_selection().connect('changed', self.on_tree_databases_selection_changed) self.treeDatabases.get_selection().connect('changed', self.on_tree_databases_selection_changed)
# layout widgets # layout widgets
@ -438,11 +594,12 @@ class WidgetDatabaseManager(gtk.VBox):
hbox.set_homogeneous(False) hbox.set_homogeneous(False)
vbox = gtk.VBox() vbox = gtk.VBox()
hbox.pack_start(vbox, False, False, 2) hbox.pack_start(vbox, False, False, 2)
vbox.pack_start(self.buttonDatabaseActivate, False, False, 2)
vbox.pack_start(self.buttonDatabaseNew, False, False, 2) vbox.pack_start(self.buttonDatabaseNew, False, False, 2)
vbox.pack_start(self.buttonDatabaseAdd, False, False, 2) vbox.pack_start(self.buttonDatabaseAdd, False, False, 2)
vbox.pack_start(self.buttonDatabaseEdit, False, False, 2) vbox.pack_start(self.buttonDatabaseEdit, False, False, 2)
vbox.pack_start(self.buttonDatabaseRemove, False, False, 2) vbox.pack_start(self.buttonDatabaseRemove, False, False, 2)
vbox.pack_start(self.buttonDatabaseDelete, False, False, 2) #vbox.pack_start(self.buttonDatabaseDelete, False, False, 2)
box = gtk.VBox() box = gtk.VBox()
vbox.pack_start(box, True, True, 0) vbox.pack_start(box, True, True, 0)
@ -452,48 +609,128 @@ class WidgetDatabaseManager(gtk.VBox):
self.show_all() self.show_all()
# init widget # init widget
model = self.treeDatabases.get_model()
for database in self.databaseManager: for database in self.databaseManager:
self.treeDatabases.get_model().append( (database.name, 'foo', database.Type, self.databaseManager.database_id(database)) ) it = model.append()
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] )
model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() )
model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database))
def on_database_manager_database_activated(self, databaseManager, idDatabase):
database = self.databaseManager.database_from_id(idDatabase)
model = self.treeDatabases.get_model()
for row in iter(model):
if row[self.treeDatabaseColumns['_id']] == idDatabase:
row[self.treeDatabaseColumns['status']] = self.databaseStatusNames[database.StatusActive]
break
else:
raise ValueError('database not found')
def on_database_manager_database_deactivated(self, databaseManager, idDatabase):
database = self.databaseManager.database_from_id(idDatabase)
model = self.treeDatabases.get_model()
for row in iter(model):
if row[self.treeDatabaseColumns['_id']] == idDatabase:
row[self.treeDatabaseColumns['status']] = self.databaseStatusNames[database.StatusInactive]
break
else:
raise ValueError('database not found')
def on_button_database_activate_clicked(self, button):
selection = self.treeDatabases.get_selection()
if selection is None:
return
model, it = selection.get_selected()
idDatabase = model.get_value(it, self.treeDatabaseColumns['_id'])
database = self.databaseManager.database_from_id(idDatabase)
self.databaseManager.activate_database(database)
#TODO: for some reason i have to click OK/Cancel twice to close the dialog #TODO: for some reason i have to click OK/Cancel twice to close the dialog
def on_button_database_new_clicked(self, button): def on_button_database_new_clicked(self, button):
databaseType = self.databaseManager.get_default_database_type() databaseKlass = self.databaseManager.get_default_database_type()
if databaseType is None: if databaseKlass is None:
raise ValueError('no defult database type set') raise ValueError('no default database type set')
database = databaseKlass()
while True:
dlg = DialogDatabaseProperties( dlg = DialogDatabaseProperties(
self.databaseManager, self.databaseManager,
databaseType(), database,
parent=self.parentWidget, parent=self.parentWidget,
mode=WidgetDatabaseProperties.ModeNew, mode=WidgetDatabaseProperties.ModeNew,
title='[New database] - database properties' title='New database'
) )
if dlg.run() == gtk.RESPONSE_REJECT: response = dlg.run()
pass if response == gtk.RESPONSE_ACCEPT:
if dlg.run() == gtk.RESPONSE_ACCEPT:
database = dlg.get_widget_database_properties().get_database() database = dlg.get_widget_database_properties().get_database()
#TODO: sanity checks + init databse if necessary #TODO: initing may or may not take a while. how to handle?
self.databaseManager.add_database(database) error = database.init_new_database()
self.treeDatabases.get_model().append( (database.name, 'foo', database.Type, self.databaseManager.database_id(database)) ) if error:
DialogError(parent=dlg, msg=error)
dlg.destroy() dlg.destroy()
continue
else:
database = None
dlg.destroy()
break
if database is None:
return
self.databaseManager.add_database(database)
model = self.treeDatabases.get_model()
it = model.append()
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] )
model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() )
model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database))
def on_button_database_add_clicked(self, button): def on_button_database_add_clicked(self, button):
databaseType = self.databaseManager.get_default_database_type() databaseKlass = self.databaseManager.get_default_database_type()
if databaseType is None: if databaseKlass is None:
raise ValueError('no defult database type set') raise ValueError('no defult database type set')
database = databaseKlass()
while True:
dlg = DialogDatabaseProperties( dlg = DialogDatabaseProperties(
self.databaseManager, self.databaseManager,
databaseType(), database,
parent=self.parentWidget, parent=self.parentWidget,
mode=WidgetDatabaseProperties.ModeAdd, mode=WidgetDatabaseProperties.ModeAdd,
title='[Add database] - database properties' title='Add database'
) )
if dlg.run() == gtk.RESPONSE_REJECT: response = dlg.run()
pass if response == gtk.RESPONSE_ACCEPT:
if dlg.run() == gtk.RESPONSE_ACCEPT:
database = dlg.get_widget_database_properties().get_database() database = dlg.get_widget_database_properties().get_database()
#TODO: sanity checks #TODO: validating may or may not take a while. how to handle?
error = database.validate_database()
if error:
DialogError(parent=self.parentWidget, msg=error)
dlg.destroy()
continue
else:
database = None
dlg.destroy()
break
if database is None:
return
self.databaseManager.add_database(database) self.databaseManager.add_database(database)
self.treeDatabases.get_model().append( (database.name, 'foo', database.Type, self.databaseManager.database_id(database)) ) model = self.treeDatabases.get_model()
it = model.append()
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] )
model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() )
model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database))
dlg.destroy() dlg.destroy()
def on_button_database_edit_clicked(self, button): def on_button_database_edit_clicked(self, button):
@ -501,39 +738,52 @@ class WidgetDatabaseManager(gtk.VBox):
if selection is None: if selection is None:
return return
model, iter = selection.get_selected() model, it = selection.get_selected()
idDatabase = model.get_value(iter, self.treeDatabaseColumns['_id']) idDatabase = model.get_value(it, self.treeDatabaseColumns['_id'])
database = self.databaseManager.database_from_id(idDatabase) database = self.databaseManager.database_from_id(idDatabase)
dlg = DialogDatabaseProperties( dlg = DialogDatabaseProperties(
self.databaseManager, self.databaseManager,
database=database, database,
parent=self.parentWidget, parent=self.parentWidget,
mode=WidgetDatabaseProperties.ModeEdit, mode=WidgetDatabaseProperties.ModeEdit,
title='[Edit database] - database properties' title='Edit database'
) )
if dlg.run() == gtk.RESPONSE_REJECT: response = dlg.run()
if response == gtk.RESPONSE_REJECT:
pass pass
if dlg.run() == gtk.RESPONSE_ACCEPT: elif response == gtk.RESPONSE_ACCEPT:
database = dlg.get_database() database = dlg.get_database()
selection = self.treeDatabases.get_selection() selection = self.treeDatabases.get_selection()
if selection is not None: if selection is not None:
model, iter = selection.get_selected() model, it = selection.get_selected()
model.set_value(iter, 0, database.name) model.set_value(it, self.treeDatabaseColumns['name'], database.name)
dlg.destroy() dlg.destroy()
def on_button_database_remove_clicked(self, button):
selection = self.treeDatabases.get_selection()
if selection is None:
return
model, it = selection.get_selected()
#TODO: finalize database
model.remove(it)
def on_tree_databases_selection_changed(self, treeSelection): def on_tree_databases_selection_changed(self, treeSelection):
hasSelection = bool(treeSelection.count_selected_rows()) hasSelection = bool(treeSelection.count_selected_rows())
# enable/disable selection dependend widgets # enable/disable selection dependend widgets
self.buttonDatabaseActivate.set_sensitive(hasSelection)
self.buttonDatabaseEdit.set_sensitive(hasSelection) self.buttonDatabaseEdit.set_sensitive(hasSelection)
self.buttonDatabaseRemove.set_sensitive(hasSelection) self.buttonDatabaseRemove.set_sensitive(hasSelection)
self.buttonDatabaseDelete.set_sensitive(hasSelection) #self.buttonDatabaseDelete.set_sensitive(hasSelection)
class DialogDatabaseManager(gtk.Dialog): class DialogDatabaseManager(gtk.Dialog):
def __init__(self, databaseManager, parent=None): def __init__(self, databaseManager, parent=None):
gtk.Dialog.__init__(self, gtk.Dialog.__init__(self,
title="My dialog", title="Databases",
parent=parent, parent=parent,
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
buttons=( buttons=(

View File

@ -18,6 +18,13 @@
#fpdb modules #fpdb modules
import Card import Card
DEBUG = True
if DEBUG:
import pprint
pp = pprint.PrettyPrinter(indent=4)
class DerivedStats(): class DerivedStats():
def __init__(self, hand): def __init__(self, hand):
self.hand = hand self.hand = hand
@ -30,13 +37,19 @@ class DerivedStats():
for player in hand.players: for player in hand.players:
self.handsplayers[player[1]] = {} self.handsplayers[player[1]] = {}
#Init vars that may not be used, but still need to be inserted. #Init vars that may not be used, but still need to be inserted.
# All stud street4 need this when importing holdem
self.handsplayers[player[1]]['winnings'] = 0
self.handsplayers[player[1]]['street4Seen'] = False
self.handsplayers[player[1]]['street4Aggr'] = False self.handsplayers[player[1]]['street4Aggr'] = False
self.assembleHands(self.hand) self.assembleHands(self.hand)
self.assembleHandsPlayers(self.hand) self.assembleHandsPlayers(self.hand)
print "hands =", self.hands if DEBUG:
print "handsplayers =", self.handsplayers print "Hands:"
pp.pprint(self.hands)
print "HandsPlayers:"
pp.pprint(self.handsplayers)
def getHands(self): def getHands(self):
return self.hands return self.hands
@ -85,11 +98,20 @@ class DerivedStats():
# commentTs DATETIME # commentTs DATETIME
def assembleHandsPlayers(self, hand): def assembleHandsPlayers(self, hand):
#street0VPI/vpip already called in Hand
#hand.players = [[seat, name, chips],[seat, name, chips]] #hand.players = [[seat, name, chips],[seat, name, chips]]
for player in hand.players: for player in hand.players:
self.handsplayers[player[1]]['seatNo'] = player[0] self.handsplayers[player[1]]['seatNo'] = player[0]
self.handsplayers[player[1]]['startCash'] = player[2] self.handsplayers[player[1]]['startCash'] = player[2]
# Winnings is a non-negative value of money collected from the pot, which already includes the
# rake taken out. hand.collectees is Decimal, database requires cents
for player in hand.collectees:
self.handsplayers[player]['winnings'] = int(100 * hand.collectees[player])
for i, street in enumerate(hand.actionStreets[2:]):
self.seen(self.hand, i+1)
for i, street in enumerate(hand.actionStreets[1:]): for i, street in enumerate(hand.actionStreets[1:]):
self.aggr(self.hand, i) self.aggr(self.hand, i)
@ -794,9 +816,9 @@ class DerivedStats():
for player in hand.players: for player in hand.players:
if player[1] in vpipers: if player[1] in vpipers:
self.handsplayers[player[1]]['vpip'] = True self.handsplayers[player[1]]['street0VPI'] = True
else: else:
self.handsplayers[player[1]]['vpip'] = False self.handsplayers[player[1]]['street0VPI'] = False
def playersAtStreetX(self, hand): def playersAtStreetX(self, hand):
""" playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4/draw1 */""" """ playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4/draw1 */"""
@ -833,6 +855,17 @@ class DerivedStats():
self.hands['street3Raises'] = 0 # /* num big bets paid to see sd/street7 */ self.hands['street3Raises'] = 0 # /* num big bets paid to see sd/street7 */
self.hands['street4Raises'] = 0 # /* num big bets paid to see showdown */ self.hands['street4Raises'] = 0 # /* num big bets paid to see showdown */
def seen(self, hand, i):
pas = set()
for act in hand.actions[hand.actionStreets[i+1]]:
pas.add(act[0])
for player in hand.players:
if player[1] in pas:
self.handsplayers[player[1]]['street%sSeen' % i] = True
else:
self.handsplayers[player[1]]['street%sSeen' % i] = False
def aggr(self, hand, i): def aggr(self, hand, i):
aggrers = set() aggrers = set()
for act in hand.actions[hand.actionStreets[i]]: for act in hand.actions[hand.actionStreets[i]]:

View File

@ -570,8 +570,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" db_type="fpdb"></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_type="fpdb" db_user="fpdb"/> --> <!-- <database db_ip="localhost" db_name="fpdb" db_pass="fpdb" db_server="sqlite" db_user="fpdb"/> -->
</supported_databases> </supported_databases>
</FreePokerToolsConfig> </FreePokerToolsConfig>

View File

@ -37,7 +37,8 @@ if __name__== "__main__":
HUD_main.config = Configuration.Config() HUD_main.config = Configuration.Config()
gobject.threads_init() # this is required gobject.threads_init() # this is required
thread.start_new_thread(HUD_main.read_stdin, ()) # starts the thread hud = HUD_main.HUD_main()
thread.start_new_thread(hud.read_stdin, ()) # starts the thread
HUD_main.main_window = gtk.Window() HUD_main.main_window = gtk.Window()
HUD_main.main_window.connect("destroy", destroy) HUD_main.main_window.connect("destroy", destroy)

View File

@ -32,19 +32,12 @@ from xml.dom.minidom import Node
import time import time
import datetime import datetime
from Exceptions import FpdbParseError from Exceptions import FpdbParseError
import Configuration
import gettext import gettext
gettext.install('fpdb') gettext.install('fpdb')
import logging, logging.config log = Configuration.get_logger("logging.conf")
import ConfigParser
try:
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
except ConfigParser.NoSectionError: # debian package path
logging.config.fileConfig('/usr/share/python-fpdb/logging.conf')
log = logging.getLogger("parser")
import pygtk import pygtk
import gtk import gtk

View File

@ -92,7 +92,7 @@ class PokerStars(HandHistoryConverter):
self.compiledPlayers = players self.compiledPlayers = players
player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")"
subst = {'PLYR': player_re, 'CUR': self.sym[hand.gametype['currency']]} subst = {'PLYR': player_re, 'CUR': self.sym[hand.gametype['currency']]}
logging.debug("player_re: " + player_re) log.debug("player_re: " + player_re)
self.re_PostSB = re.compile(r"^%(PLYR)s: posts small blind %(CUR)s(?P<SB>[.0-9]+)" % subst, re.MULTILINE) self.re_PostSB = re.compile(r"^%(PLYR)s: posts small blind %(CUR)s(?P<SB>[.0-9]+)" % subst, re.MULTILINE)
self.re_PostBB = re.compile(r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[.0-9]+)" % subst, re.MULTILINE) self.re_PostBB = re.compile(r"^%(PLYR)s: posts big blind %(CUR)s(?P<BB>[.0-9]+)" % subst, re.MULTILINE)
self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante %(CUR)s(?P<ANTE>[.0-9]+)" % subst, re.MULTILINE) self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante %(CUR)s(?P<ANTE>[.0-9]+)" % subst, re.MULTILINE)
@ -186,7 +186,7 @@ class PokerStars(HandHistoryConverter):
# m = self.re_Button.search(hand.handText) # m = self.re_Button.search(hand.handText)
# if m: info.update(m.groupdict()) # if m: info.update(m.groupdict())
# TODO : I rather like the idea of just having this dict as hand.info # TODO : I rather like the idea of just having this dict as hand.info
logging.debug("readHandInfo: %s" % info) log.debug("readHandInfo: %s" % info)
for key in info: for key in info:
if key == 'DATETIME': if key == 'DATETIME':
#2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] #2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET]
@ -226,10 +226,10 @@ class PokerStars(HandHistoryConverter):
if m: if m:
hand.buttonpos = int(m.group('BUTTON')) hand.buttonpos = int(m.group('BUTTON'))
else: else:
logging.info('readButton: not found') log.info('readButton: not found')
def readPlayerStacks(self, hand): def readPlayerStacks(self, hand):
logging.debug("readPlayerStacks") log.debug("readPlayerStacks")
m = self.re_PlayerInfo.finditer(hand.handText) m = self.re_PlayerInfo.finditer(hand.handText)
players = [] players = []
for a in m: for a in m:
@ -265,7 +265,7 @@ class PokerStars(HandHistoryConverter):
hand.setCommunityCards(street, m.group('CARDS').split(' ')) hand.setCommunityCards(street, m.group('CARDS').split(' '))
def readAntes(self, hand): def readAntes(self, hand):
logging.debug("reading antes") log.debug("reading antes")
m = self.re_Antes.finditer(hand.handText) m = self.re_Antes.finditer(hand.handText)
for player in m: for player in m:
#~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE'))) #~ logging.debug("hand.addAnte(%s,%s)" %(player.group('PNAME'), player.group('ANTE')))

View File

@ -33,12 +33,11 @@ import re
class Sql: class Sql:
def __init__(self, game = 'holdem', type = 'fpdb', db_server = 'mysql'): def __init__(self, game = 'holdem', db_server = 'mysql'):
self.query = {} self.query = {}
###############################################################################3 ###############################################################################3
# Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/ # Support for the Free Poker DataBase = fpdb http://fpdb.sourceforge.net/
# #
if type == 'fpdb':
################################ ################################
# List tables # List tables
@ -213,7 +212,7 @@ class Sql:
if db_server == 'mysql': if db_server == 'mysql':
self.query['createHandsTable'] = """CREATE TABLE Hands ( self.query['createHandsTable'] = """CREATE TABLE Hands (
id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id),
tableName VARCHAR(20) NOT NULL, tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL, siteHandNo BIGINT 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,
@ -248,7 +247,7 @@ class Sql:
elif db_server == 'postgresql': elif db_server == 'postgresql':
self.query['createHandsTable'] = """CREATE TABLE Hands ( self.query['createHandsTable'] = """CREATE TABLE Hands (
id BIGSERIAL, PRIMARY KEY (id), id BIGSERIAL, PRIMARY KEY (id),
tableName VARCHAR(20) NOT NULL, tableName VARCHAR(22) NOT NULL,
siteHandNo BIGINT NOT NULL, siteHandNo BIGINT 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,
@ -282,7 +281,7 @@ class Sql:
elif db_server == 'sqlite': elif db_server == 'sqlite':
self.query['createHandsTable'] = """CREATE TABLE Hands ( self.query['createHandsTable'] = """CREATE TABLE Hands (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
tableName TEXT(20) NOT NULL, tableName TEXT(22) NOT NULL,
siteHandNo INT NOT NULL, siteHandNo INT NOT NULL,
gametypeId INT NOT NULL, gametypeId INT NOT NULL,
handStart REAL NOT NULL, handStart REAL NOT NULL,
@ -1646,15 +1645,6 @@ class Sql:
where Id = %s where Id = %s
""" """
self.query['get_action_from_hand'] = """
SELECT street, Players.name, HandsActions.action, HandsActions.amount, actionno
FROM Players, HandsActions, HandsPlayers
WHERE HandsPlayers.handid = %s
AND HandsPlayers.playerid = Players.id
AND HandsActions.handsPlayerId = HandsPlayers.id
ORDER BY street, actionno
"""
if db_server == 'mysql': if db_server == 'mysql':
self.query['get_hand_1day_ago'] = """ self.query['get_hand_1day_ago'] = """
select coalesce(max(id),0) select coalesce(max(id),0)
@ -1974,6 +1964,271 @@ class Sql:
,s.name ,s.name
""" """
if db_server == 'mysql':
self.query['playerStats'] = """
SELECT
concat(upper(stats.limitType), ' '
,concat(upper(substring(stats.category,1,1)),substring(stats.category,2) ), ' '
,stats.name, ' '
,cast(stats.bigBlindDesc as char)
) AS Game
,stats.n
,stats.vpip
,stats.pfr
,stats.pf3
,stats.steals
,stats.saw_f
,stats.sawsd
,stats.wtsdwsf
,stats.wmsd
,stats.FlAFq
,stats.TuAFq
,stats.RvAFq
,stats.PoFAFq
,stats.Net
,stats.BBper100
,stats.Profitperhand
,case when hprof2.variance = -999 then '-'
else format(hprof2.variance, 2)
end AS Variance
,stats.AvgSeats
FROM
(select /* stats from hudcache */
gt.base
,gt.category
,upper(gt.limitType) as limitType
,s.name
,<selectgt.bigBlind> AS bigBlindDesc
,<hcgametypeId> AS gtId
,sum(HDs) AS n
,format(100.0*sum(street0VPI)/sum(HDs),1) AS vpip
,format(100.0*sum(street0Aggr)/sum(HDs),1) AS pfr
,case when sum(street0_3Bchance) = 0 then '0'
else format(100.0*sum(street0_3Bdone)/sum(street0_3Bchance),1)
end AS pf3
,case when sum(stealattemptchance) = 0 then '-'
else format(100.0*sum(stealattempted)/sum(stealattemptchance),1)
end AS steals
,format(100.0*sum(street1Seen)/sum(HDs),1) AS saw_f
,format(100.0*sum(sawShowdown)/sum(HDs),1) AS sawsd
,case when sum(street1Seen) = 0 then '-'
else format(100.0*sum(sawShowdown)/sum(street1Seen),1)
end AS wtsdwsf
,case when sum(sawShowdown) = 0 then '-'
else format(100.0*sum(wonAtSD)/sum(sawShowdown),1)
end AS wmsd
,case when sum(street1Seen) = 0 then '-'
else format(100.0*sum(street1Aggr)/sum(street1Seen),1)
end AS FlAFq
,case when sum(street2Seen) = 0 then '-'
else format(100.0*sum(street2Aggr)/sum(street2Seen),1)
end AS TuAFq
,case when sum(street3Seen) = 0 then '-'
else format(100.0*sum(street3Aggr)/sum(street3Seen),1)
end AS RvAFq
,case when sum(street1Seen)+sum(street2Seen)+sum(street3Seen) = 0 then '-'
else format(100.0*(sum(street1Aggr)+sum(street2Aggr)+sum(street3Aggr))
/(sum(street1Seen)+sum(street2Seen)+sum(street3Seen)),1)
end AS PoFAFq
,format(sum(totalProfit)/100.0,2) AS Net
,format((sum(totalProfit/(gt.bigBlind+0.0))) / (sum(HDs)/100.0),2)
AS BBper100
,format( (sum(totalProfit)/100.0) / sum(HDs), 4) AS Profitperhand
,format( sum(activeSeats*HDs)/(sum(HDs)+0.0), 2) AS AvgSeats
from Gametypes gt
inner join Sites s on s.Id = gt.siteId
inner join HudCache hc on hc.gameTypeId = gt.Id
where hc.playerId in <player_test>
and <gtbigBlind_test>
and hc.activeSeats <seats_test>
and concat( '20', substring(hc.styleKey,2,2), '-', substring(hc.styleKey,4,2), '-'
, substring(hc.styleKey,6,2) ) <datestest>
group by gt.base
,gt.category
<groupbyseats>
,plposition
,upper(gt.limitType)
,s.name
having 1 = 1 <havingclause>
order by pname
,gt.base
,gt.category
<orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
else concat('Z', <position>)
end
<orderbyhgameTypeId>
,upper(gt.limitType) desc
,maxbigblind desc
,s.name
"""
elif db_server == 'postgresql':
self.query['playerDetailedStats'] = """
select <hgameTypeId> AS hgametypeid
,<playerName> AS pname
,gt.base
,gt.category
,upper(gt.limitType) AS limittype
,s.name
,min(gt.bigBlind) AS minbigblind
,max(gt.bigBlind) AS maxbigblind
/*,<hcgametypeId> AS gtid*/
,<position> AS plposition
,count(1) AS n
,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip
,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr
,case when sum(cast(hp.street0_3Bchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street0_3Bdone as <signed>integer))/sum(cast(hp.street0_3Bchance as <signed>integer))
end AS pf3
,case when sum(cast(hp.stealattemptchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.stealattempted as <signed>integer))/sum(cast(hp.stealattemptchance as <signed>integer))
end AS steals
,100.0*sum(cast(hp.street1Seen as <signed>integer))/count(1) AS saw_f
,100.0*sum(cast(hp.sawShowdown as <signed>integer))/count(1) AS sawsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.sawShowdown as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS wtsdwsf
,case when sum(cast(hp.sawShowdown as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.wonAtSD as <signed>integer))/sum(cast(hp.sawShowdown as <signed>integer))
end AS wmsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street1Aggr as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS flafq
,case when sum(cast(hp.street2Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street2Aggr as <signed>integer))/sum(cast(hp.street2Seen as <signed>integer))
end AS tuafq
,case when sum(cast(hp.street3Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street3Aggr as <signed>integer))/sum(cast(hp.street3Seen as <signed>integer))
end AS rvafq
,case when sum(cast(hp.street1Seen as <signed>integer))+sum(cast(hp.street2Seen as <signed>integer))+sum(cast(hp.street3Seen as <signed>integer)) = 0 then -999
else 100.0*(sum(cast(hp.street1Aggr as <signed>integer))+sum(cast(hp.street2Aggr as <signed>integer))+sum(cast(hp.street3Aggr as <signed>integer)))
/(sum(cast(hp.street1Seen as <signed>integer))+sum(cast(hp.street2Seen as <signed>integer))+sum(cast(hp.street3Seen as <signed>integer)))
end AS pofafq
,sum(hp.totalProfit)/100.0 AS net
,sum(hp.rake)/100.0 AS rake
,100.0*avg(hp.totalProfit/(gt.bigBlind+0.0)) AS bbper100
,avg(hp.totalProfit)/100.0 AS profitperhand
,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr
,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr
,avg(h.seats+0.0) AS avgseats
,variance(hp.totalProfit/100.0) AS variance
from HandsPlayers hp
inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId)
inner join Sites s on (s.Id = gt.siteId)
inner join Players p on (p.Id = hp.playerId)
where hp.playerId in <player_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
<gtbigBlind_test>
and to_char(h.handStart, 'YYYY-MM-DD') <datestest>
group by hgameTypeId
,pname
,gt.base
,gt.category
<groupbyseats>
,plposition
,upper(gt.limitType)
,s.name
having 1 = 1 <havingclause>
order by pname
,gt.base
,gt.category
<orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
when '0' then 'Y'
else 'Z'||<position>
end
<orderbyhgameTypeId>
,upper(gt.limitType) desc
,maxbigblind desc
,s.name
"""
elif db_server == 'sqlite':
self.query['playerDetailedStats'] = """
select <hgameTypeId> AS hgametypeid
,gt.base
,gt.category
,upper(gt.limitType) AS limittype
,s.name
,min(gt.bigBlind) AS minbigblind
,max(gt.bigBlind) AS maxbigblind
/*,<hcgametypeId> AS gtid*/
,<position> AS plposition
,count(1) AS n
,100.0*sum(cast(hp.street0VPI as <signed>integer))/count(1) AS vpip
,100.0*sum(cast(hp.street0Aggr as <signed>integer))/count(1) AS pfr
,case when sum(cast(hp.street0_3Bchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street0_3Bdone as <signed>integer))/sum(cast(hp.street0_3Bchance as <signed>integer))
end AS pf3
,case when sum(cast(hp.stealattemptchance as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.stealattempted as <signed>integer))/sum(cast(hp.stealattemptchance as <signed>integer))
end AS steals
,100.0*sum(cast(hp.street1Seen as <signed>integer))/count(1) AS saw_f
,100.0*sum(cast(hp.sawShowdown as <signed>integer))/count(1) AS sawsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.sawShowdown as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS wtsdwsf
,case when sum(cast(hp.sawShowdown as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.wonAtSD as <signed>integer))/sum(cast(hp.sawShowdown as <signed>integer))
end AS wmsd
,case when sum(cast(hp.street1Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street1Aggr as <signed>integer))/sum(cast(hp.street1Seen as <signed>integer))
end AS flafq
,case when sum(cast(hp.street2Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street2Aggr as <signed>integer))/sum(cast(hp.street2Seen as <signed>integer))
end AS tuafq
,case when sum(cast(hp.street3Seen as <signed>integer)) = 0 then -999
else 100.0*sum(cast(hp.street3Aggr as <signed>integer))/sum(cast(hp.street3Seen as <signed>integer))
end AS rvafq
,case when sum(cast(hp.street1Seen as <signed>integer))+sum(cast(hp.street2Seen as <signed>integer))+sum(cast(hp.street3Seen as <signed>integer)) = 0 then -999
else 100.0*(sum(cast(hp.street1Aggr as <signed>integer))+sum(cast(hp.street2Aggr as <signed>integer))+sum(cast(hp.street3Aggr as <signed>integer)))
/(sum(cast(hp.street1Seen as <signed>integer))+sum(cast(hp.street2Seen as <signed>integer))+sum(cast(hp.street3Seen as <signed>integer)))
end AS pofafq
,sum(hp.totalProfit)/100.0 AS net
,sum(hp.rake)/100.0 AS rake
,100.0*avg(hp.totalProfit/(gt.bigBlind+0.0)) AS bbper100
,avg(hp.totalProfit)/100.0 AS profitperhand
,100.0*avg((hp.totalProfit+hp.rake)/(gt.bigBlind+0.0)) AS bb100xr
,avg((hp.totalProfit+hp.rake)/100.0) AS profhndxr
,avg(h.seats+0.0) AS avgseats
,variance(hp.totalProfit/100.0) AS variance
from HandsPlayers hp
inner join Hands h on (h.id = hp.handId)
inner join Gametypes gt on (gt.Id = h.gameTypeId)
inner join Sites s on (s.Id = gt.siteId)
where hp.playerId in <player_test>
/*and hp.tourneysPlayersId IS NULL*/
and h.seats <seats_test>
<flagtest>
<gtbigBlind_test>
and to_char(h.handStart, 'YYYY-MM-DD') <datestest>
group by hgameTypeId
,hp.playerId
,gt.base
,gt.category
<groupbyseats>
,plposition
,upper(gt.limitType)
,s.name
order by hp.playerId
,gt.base
,gt.category
<orderbyseats>
,case <position> when 'B' then 'B'
when 'S' then 'S'
when '0' then 'Y'
else 'Z'||<position>
end
<orderbyhgameTypeId>
,upper(gt.limitType) desc
,maxbigblind desc
,s.name
"""
if db_server == 'mysql': if db_server == 'mysql':
self.query['playerStats'] = """ self.query['playerStats'] = """
SELECT SELECT
@ -2476,6 +2731,25 @@ class Sql:
GROUP BY h.handStart, hp.handId, hp.totalProfit GROUP BY h.handStart, hp.handId, hp.totalProfit
ORDER BY h.handStart""" ORDER BY h.handStart"""
####################################
# Session stats query
####################################
if db_server == 'mysql':
self.query['sessionStats'] = """
SELECT UNIX_TIMESTAMP(h.handStart) as time, hp.handId, hp.startCash, hp.winnings, hp.totalProfit
FROM HandsPlayers hp
INNER JOIN Players pl ON (pl.id = hp.playerId)
INNER JOIN Hands h ON (h.id = hp.handId)
INNER JOIN Gametypes gt ON (gt.id = h.gametypeId)
WHERE pl.id in <player_test>
AND pl.siteId in <site_test>
AND h.handStart > '<startdate_test>'
AND h.handStart < '<enddate_test>'
<limit_test>
AND hp.tourneysPlayersId IS NULL
GROUP BY h.handStart, hp.handId, hp.totalProfit
ORDER BY h.handStart"""
#################################### ####################################
# Session stats query # Session stats query
#################################### ####################################
@ -2669,6 +2943,8 @@ class Sql:
,hp.tourneyTypeId ,hp.tourneyTypeId
,date_format(h.handStart, 'd%y%m%d') ,date_format(h.handStart, 'd%y%m%d')
""" """
#>>>>>>> 28ca49d592c8e706ad6ee58dd26655bcc33fc5fb:pyfpdb/SQL.py
#"""
elif db_server == 'postgresql': elif db_server == 'postgresql':
self.query['rebuildHudCache'] = """ self.query['rebuildHudCache'] = """
INSERT INTO HudCache INSERT INTO HudCache

View File

@ -450,7 +450,7 @@ class fpdb:
if self.db is not None and self.db.fdb is not None: if self.db is not None and self.db.fdb is not None:
self.db.disconnect() self.db.disconnect()
self.sql = SQL.Sql(type = self.settings['db-type'], db_server = self.settings['db-server']) self.sql = SQL.Sql(db_server = self.settings['db-server'])
try: try:
self.db = Database.Database(self.config, sql = self.sql) self.db = Database.Database(self.config, sql = self.sql)
except FpdbMySQLFailedError: except FpdbMySQLFailedError:

View File

@ -33,12 +33,12 @@ except ImportError:
import fpdb_simple import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
import Configuration
class fpdb_db: class fpdb_db:
MYSQL_INNODB = 2 MYSQL_INNODB = 2
PGSQL = 3 PGSQL = 3
SQLITE = 4 SQLITE = 4
sqlite_db_dir = ".." + os.sep + "database"
def __init__(self): def __init__(self):
"""Simple constructor, doesnt really do anything""" """Simple constructor, doesnt really do anything"""
@ -123,10 +123,10 @@ class fpdb_db:
else: else:
logging.warning("SQLite won't work well without 'sqlalchemy' installed.") logging.warning("SQLite won't work well without 'sqlalchemy' installed.")
if not os.path.isdir(self.sqlite_db_dir): if not os.path.isdir(Configuration.DIR_DATABASES):
print "Creating directory: '%s'" % (self.sqlite_db_dir) print "Creating directory: '%s'" % (Configuration.DIR_DATABASES)
os.mkdir(self.sqlite_db_dir) os.mkdir(Configuration.DIR_DATABASES)
self.db = sqlite3.connect( self.sqlite_db_dir + os.sep + database self.db = sqlite3.connect( os.path.join(Configuration.DIR_DATABASES, database)
, detect_types=sqlite3.PARSE_DECLTYPES ) , detect_types=sqlite3.PARSE_DECLTYPES )
sqlite3.register_converter("bool", lambda x: bool(int(x))) sqlite3.register_converter("bool", lambda x: bool(int(x)))
sqlite3.register_adapter(bool, lambda x: "1" if x else "0") sqlite3.register_adapter(bool, lambda x: "1" if x else "0")

View File

@ -42,15 +42,7 @@ import fpdb_parse_logic
import Configuration import Configuration
import Exceptions import Exceptions
import logging, logging.config log = Configuration.get_logger("logging.conf", "importer")
import ConfigParser
try:
logging.config.fileConfig(os.path.join(sys.path[0],"logging.conf"))
except ConfigParser.NoSectionError: # debian package path
logging.config.fileConfig('/usr/share/python-fpdb/logging.conf')
log = logging.getLogger('importer')
# database interface modules # database interface modules
try: try:

View File

@ -60,13 +60,9 @@ class InterProcessLockBase:
self._has_lock = False self._has_lock = False
def locked(self): def locked(self):
if self._has_lock: if self.acquire():
return True
try:
self.acquire()
self.release() self.release()
return False return False
except SingleInstanceError:
return True return True
LOCK_FILE_DIRECTORY = '/tmp' LOCK_FILE_DIRECTORY = '/tmp'
@ -162,38 +158,33 @@ def test_construct():
>>> lock1 = InterProcessLock(name=test_name) >>> lock1 = InterProcessLock(name=test_name)
>>> lock1.acquire() >>> lock1.acquire()
True
>>> lock2 = InterProcessLock(name=test_name) >>> lock2 = InterProcessLock(name=test_name)
>>> lock3 = InterProcessLock(name=test_name) >>> lock3 = InterProcessLock(name=test_name)
# Since lock1 is locked, other attempts to acquire it fail. # Since lock1 is locked, other attempts to acquire it fail.
>>> lock2.acquire() >>> lock2.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> lock3.acquire() >>> lock3.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release the lock and let lock2 have it. # Release the lock and let lock2 have it.
>>> lock1.release() >>> lock1.release()
>>> lock2.acquire() >>> lock2.acquire()
True
>>> lock3.acquire() >>> lock3.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Release it and give it back to lock1 # Release it and give it back to lock1
>>> lock2.release() >>> lock2.release()
>>> lock1.acquire() >>> lock1.acquire()
True
>>> lock2.acquire() >>> lock2.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
# Test lock status # Test lock status
>>> lock2.locked() >>> lock2.locked()
@ -224,7 +215,7 @@ def test_construct():
... import win32con ... import win32con
... import pywintypes ... import pywintypes
... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid) ... handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE , pywintypes.FALSE, pid)
... return (0 != win32api.TerminateProcess(handle, 0)) ... #return (0 != win32api.TerminateProcess(handle, 0))
# Test to acquire the lock in another process. # Test to acquire the lock in another process.
>>> def execute(cmd): >>> def execute(cmd):
@ -237,15 +228,14 @@ def test_construct():
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();') >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire() >>> lock1.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid) >>> os_independent_kill(pid)
>>> time.sleep(1) >>> time.sleep(1)
>>> lock1.acquire() >>> lock1.acquire()
True
>>> lock1.release() >>> lock1.release()
# Testing wait # Testing wait
@ -253,13 +243,12 @@ def test_construct():
>>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();') >>> pid = execute('import interlocks;a=interlocks.InterProcessLock(name=\\''+test_name+ '\\');a.acquire();')
>>> lock1.acquire() >>> lock1.acquire()
Traceback (most recent call last): False
...
SingleInstanceError: Could not acquire exclusive lock on /tmp/test.lck
>>> os_independent_kill(pid) >>> os_independent_kill(pid)
>>> lock1.acquire(True) >>> lock1.acquire(True)
True
>>> lock1.release() >>> lock1.release()
""" """

View File

@ -27,6 +27,22 @@ Py2exe script for fpdb.
# get rid of all the uneeded libraries (e.g., pyQT) # get rid of all the uneeded libraries (e.g., pyQT)
# think about an installer # think about an installer
#HOW TO USE this script:
#
# cd to the folder where this script is stored, usually .../pyfpdb.
# If there are build and dist subfolders present , delete them to get
# rid of earlier builds.
# Run the script with "py2exe_setup.py py2exe"
# You will frequently get messages about missing .dll files. E. g.,
# MSVCP90.dll. These are somewhere in your windows install, so you
# can just copy them to your working folder.
# If it works, you'll have 2 new folders, build and dist. Build is
# working space and should be deleted. Dist contains the files to be
# distributed. Last, you must copy the etc/, lib/ and share/ folders
# from your gtk/bin/ folder to the dist folder. (the whole folders, not
# just the contents) You can (should) then prune the etc/, lib/ and
# share/ folders to remove components we don't need.
from distutils.core import setup from distutils.core import setup
import py2exe import py2exe
@ -36,7 +52,8 @@ setup(
version = '0.12', version = '0.12',
console = [ {'script': 'fpdb.py', }, console = [ {'script': 'fpdb.py', },
{'script': 'HUD_main.py', } {'script': 'HUD_main.py', },
{'script': 'Configuration.py', }
], ],
options = {'py2exe': { options = {'py2exe': {
@ -47,8 +64,9 @@ setup(
} }
}, },
data_files = ['HUD_config.xml', data_files = ['HUD_config.xml.example',
'Cards01.png' 'Cards01.png',
'logging.conf'
] ]
) )