Merge branch 'master' of git://git.assembla.com/fpdb
This commit is contained in:
commit
3ed0476709
57
packaging/gentoo/fpdb-0.20-r1.ebuild
Normal file
57
packaging/gentoo/fpdb-0.20-r1.ebuild
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Copyright 1999-2010 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# created by Steffen Schaumburg, steffen@schaumburger.info
|
||||
|
||||
EAPI="2"
|
||||
NEED_PYTHON=2.5
|
||||
|
||||
#inherit distutils
|
||||
|
||||
DESCRIPTION="A database program to track your online poker games"
|
||||
HOMEPAGE="http://fpdb.sourceforge.net/"
|
||||
#SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2"
|
||||
|
||||
LICENSE="AGPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64 ~x86"
|
||||
#note: this should work on other architectures too, please send me your experiences
|
||||
|
||||
IUSE="graphing mysql postgres sqlite"
|
||||
RDEPEND="
|
||||
mysql? ( virtual/mysql
|
||||
dev-python/mysql-python )
|
||||
postgres? ( dev-db/postgresql-server
|
||||
dev-python/psycopg )
|
||||
sqlite? ( dev-lang/python[sqlite]
|
||||
dev-python/numpy )
|
||||
>=x11-libs/gtk+-2.10
|
||||
dev-python/pygtk
|
||||
graphing? ( dev-python/numpy
|
||||
dev-python/matplotlib[gtk] )
|
||||
dev-python/python-xlib"
|
||||
DEPEND="${RDEPEND}"
|
||||
|
||||
#src_install() {
|
||||
# DIRINST="${D}usr/share/games/fpdb/"
|
||||
# mkdir -p "${DIRINST}"
|
||||
# cp -R * "${DIRINST}" || die
|
||||
#
|
||||
# DIRBIN="${D}usr/games/bin/"
|
||||
# mkdir -p "${DIRBIN}"
|
||||
# #echo "pathes"
|
||||
# #echo "${DIRINST}pyfpdb/fpdb.py"
|
||||
# #echo "${DIRBIN}fpdb.py"
|
||||
# #echo
|
||||
# echo "cd /usr/share/games/fpdb/pyfpdb/ && python fpdb.py" > "${DIRBIN}fpdb" || die
|
||||
# chmod 755 "${DIRBIN}fpdb" || die
|
||||
#}
|
||||
|
||||
#src_test() {
|
||||
#}
|
||||
|
||||
pkg_postinst() {
|
||||
elog "Fpdb's dependencies have been installed. Please visit fpdb.sourceforge.net"
|
||||
elog "and download and unpack the archive.You can then start fpdb by running run_fpdb.py."
|
||||
elog "Note that if you really want to use mysql or postgresql you will have to create"
|
||||
elog "the database and user yourself and enter it into the fpdb config."
|
||||
}
|
59
packaging/gentoo/fpdb-0.20-r2.ebuild
Normal file
59
packaging/gentoo/fpdb-0.20-r2.ebuild
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright 1999-2010 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# created by Steffen Schaumburg, steffen@schaumburger.info
|
||||
|
||||
EAPI="2"
|
||||
NEED_PYTHON=2.5
|
||||
|
||||
DESCRIPTION="Fpdb is a free/open source tracker/HUD for use with online poker"
|
||||
HOMEPAGE="http://fpdb.wiki.sourceforge.net/"
|
||||
SRC_URI="mirror://sourceforge/${PN}/${PV}/${P}.tar.gz"
|
||||
|
||||
LICENSE="AGPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64 ~x86"
|
||||
#note: this should work on other architectures too, please send me your experiences
|
||||
|
||||
IUSE="graphing mysql postgres sqlite"
|
||||
RDEPEND="
|
||||
mysql? ( virtual/mysql
|
||||
dev-python/mysql-python )
|
||||
postgres? ( dev-db/postgresql-server
|
||||
dev-python/psycopg )
|
||||
sqlite? ( dev-lang/python[sqlite]
|
||||
dev-python/numpy )
|
||||
>=x11-libs/gtk+-2.10
|
||||
dev-python/pygtk
|
||||
graphing? ( dev-python/numpy
|
||||
dev-python/matplotlib[gtk] )
|
||||
dev-python/python-xlib"
|
||||
DEPEND="${RDEPEND}"
|
||||
|
||||
src_install() {
|
||||
dodir /usr/share/games/fpdb
|
||||
|
||||
exeinto /usr/share/games/fpdb
|
||||
doexe run_fpdb.py
|
||||
dosym /usr/share/games/fpdb/run_fpdb.py /usr/bin/fpdb
|
||||
|
||||
insinto /usr/share/games/fpdb
|
||||
doins readme.txt
|
||||
|
||||
insinto /usr/share/games/fpdb/files
|
||||
doins files/*
|
||||
|
||||
insinto /usr/share/games/fpdb/gfx
|
||||
doins gfx/*
|
||||
|
||||
insinto /usr/share/games/fpdb/pyfpdb
|
||||
doins pyfpdb/*
|
||||
|
||||
# pyfpdb/regression-test-files dir is missing for now; cp -r ??
|
||||
|
||||
}
|
||||
|
||||
pkg_postinst() {
|
||||
elog "Note that if you really want to use mysql or postgresql you will have to create"
|
||||
elog "the database and user yourself and enter it into the fpdb config."
|
||||
elog "You can find the instructions on the project's website."
|
||||
}
|
61
packaging/gentoo/fpdb-0.20.1.ebuild
Normal file
61
packaging/gentoo/fpdb-0.20.1.ebuild
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Copyright 1999-2010 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# created by Steffen Schaumburg, steffen@schaumburger.info
|
||||
|
||||
inherit eutils
|
||||
inherit games
|
||||
|
||||
EAPI="2"
|
||||
NEED_PYTHON=2.5
|
||||
|
||||
DESCRIPTION="A free/open source tracker/HUD for use with online poker"
|
||||
HOMEPAGE="http://fpdb.wiki.sourceforge.net/"
|
||||
SRC_URI="mirror://sourceforge/${PN}/${PV}/${P}.tar.gz"
|
||||
|
||||
LICENSE="AGPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64 ~x86"
|
||||
#note: this should work on other architectures too, please send me your experiences
|
||||
|
||||
IUSE="graph mysql postgres sqlite"
|
||||
RDEPEND="
|
||||
mysql? ( virtual/mysql
|
||||
dev-python/mysql-python )
|
||||
postgres? ( dev-db/postgresql-server
|
||||
dev-python/psycopg )
|
||||
sqlite? ( dev-lang/python[sqlite]
|
||||
dev-python/numpy )
|
||||
>=x11-libs/gtk+-2.10
|
||||
dev-python/pygtk
|
||||
graph? ( dev-python/numpy
|
||||
dev-python/matplotlib[gtk] )
|
||||
dev-python/python-xlib"
|
||||
DEPEND="${RDEPEND}"
|
||||
|
||||
src_install() {
|
||||
insinto "${GAMES_DATADIR}"/${PN}
|
||||
doins -r gfx
|
||||
doins -r pyfpdb
|
||||
doins readme.txt
|
||||
|
||||
exeinto "${GAMES_DATADIR}"/${PN}
|
||||
doexe run_fpdb.py
|
||||
|
||||
dodir "${GAMES_BINDIR}"
|
||||
dosym "${GAMES_DATADIR}"/${PN}/run_fpdb.py "${GAMES_BINDIR}"/${PN}
|
||||
|
||||
newicon gfx/fpdb-icon.png ${PN}.png
|
||||
make_desktop_entry ${PN}
|
||||
|
||||
prepgamesdirs
|
||||
fperms +x "${GAMES_DATADIR}"/${PN}/pyfpdb/*.pyw
|
||||
}
|
||||
|
||||
pkg_postinst() {
|
||||
games_pkg_postinst
|
||||
echo
|
||||
elog "Note that if you really want to use mysql or postgresql you will have to create"
|
||||
elog "the database and user yourself and enter it into the fpdb config."
|
||||
elog "You can find the instructions on the project's website."
|
||||
echo
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
# Copyright 1999-2010 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# created by Steffen Schaumburg, steffen@schaumburger.info
|
||||
|
||||
EAPI="2"
|
||||
NEED_PYTHON=2.5
|
||||
|
||||
#inherit distutils
|
||||
|
||||
DESCRIPTION="A database program to track your online poker games"
|
||||
HOMEPAGE="http://fpdb.sourceforge.net/"
|
||||
#SRC_URI="mirror://sourceforge/fpdb/${MY_P}.tar.bz2"
|
||||
|
||||
LICENSE="AGPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS="~amd64 ~x86"
|
||||
#note: this should work on other architectures too, please send me your experiences
|
||||
|
||||
IUSE="mysql postgres graphing"
|
||||
RDEPEND="
|
||||
mysql? ( virtual/mysql
|
||||
dev-python/mysql-python )
|
||||
postgres? ( dev-db/postgresql-server
|
||||
dev-python/psycopg )
|
||||
>=x11-libs/gtk+-2.10
|
||||
dev-python/pygtk
|
||||
graphing? ( dev-python/numpy
|
||||
dev-python/matplotlib[gtk] )
|
||||
dev-python/python-xlib"
|
||||
DEPEND="${RDEPEND}"
|
||||
|
||||
#src_install() {
|
||||
# DIRINST="${D}usr/share/games/fpdb/"
|
||||
# mkdir -p "${DIRINST}"
|
||||
# cp -R * "${DIRINST}" || die
|
||||
#
|
||||
# DIRBIN="${D}usr/games/bin/"
|
||||
# mkdir -p "${DIRBIN}"
|
||||
# #echo "pathes"
|
||||
# #echo "${DIRINST}pyfpdb/fpdb.py"
|
||||
# #echo "${DIRBIN}fpdb.py"
|
||||
# #echo
|
||||
# echo "cd /usr/share/games/fpdb/pyfpdb/ && python fpdb.py" > "${DIRBIN}fpdb" || die
|
||||
# chmod 755 "${DIRBIN}fpdb" || die
|
||||
#}
|
||||
|
||||
#src_test() {
|
||||
#}
|
||||
|
||||
pkg_postinst() {
|
||||
elog "Fpdb's dependencies have been installed. Please visit fpdb.sourceforge.net and download and unpack the archive."
|
||||
elog "You can then start fpdb by running run_fpdb.py. Good luck!"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -177,7 +177,7 @@ or None if we fail to get the info """
|
|||
# TODO: (1-on-1) does have that info in the game type line
|
||||
if self.HORSEHand:
|
||||
hand.maxseats = 8
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%Y-%m-%d %H:%M:%S")
|
||||
hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), "%Y-%m-%d %H:%M:%S")
|
||||
return
|
||||
|
||||
def readPlayerStacks(self, hand):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Grigorij Indigirkin
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Grigorij Indigirkin
|
||||
|
@ -50,8 +50,8 @@ class Gametype(MappedBase):
|
|||
@staticmethod
|
||||
def get_or_create(session, siteId, gametype):
|
||||
map = zip(
|
||||
['type', 'base', 'category', 'limitType', 'smallBlind', 'bigBlind', 'smallBet', 'bigBet'],
|
||||
['type', 'base', 'category', 'limitType', 'sb', 'bb', 'dummy', 'dummy', ])
|
||||
['type', 'base', 'category', 'limitType', 'smallBlind', 'bigBlind', 'smallBet', 'bigBet', 'currency'],
|
||||
['type', 'base', 'category', 'limitType', 'sb', 'bb', 'dummy', 'dummy', 'currency'])
|
||||
gametype = dict([(new, gametype.get(old)) for new, old in map ])
|
||||
|
||||
hilo = "h"
|
||||
|
@ -168,8 +168,8 @@ class HandInternal(DerivedStats):
|
|||
'speed': 'speed',
|
||||
'maxSeats': 'maxseats',
|
||||
'knockout': 'isKO',
|
||||
'rebuyOrAddon': 'isRebuy',
|
||||
'headsUp': 'isHU',
|
||||
'rebuy': 'isRebuy',
|
||||
'addOn': 'isAddOn',
|
||||
'shootout': 'isShootout',
|
||||
'matrix': 'isMatrix',
|
||||
'sng': 'isSNG',
|
||||
|
@ -193,15 +193,15 @@ class HandInternal(DerivedStats):
|
|||
setattr(tour, col, hand_val)
|
||||
elif col == 'koBounty':
|
||||
setattr(tour, col, max(db_val, hand_val))
|
||||
elif col == 'tourStartTime' and hand.handStart:
|
||||
setattr(tour, col, min(db_val, hand.handStart))
|
||||
elif col == 'tourStartTime' and hand.startTime:
|
||||
setattr(tour, col, min(db_val, hand.startTime))
|
||||
|
||||
if tour.entries is None and tour_type.sng:
|
||||
tour.entries = tour_type.maxSeats
|
||||
|
||||
# fetch and update tourney players
|
||||
for hp in self.handPlayers:
|
||||
tp = TourneyPlayer.get_or_create(session, tour.id, hp.playerId)
|
||||
tp = TourneysPlayer.get_or_create(session, tour.id, hp.playerId)
|
||||
# FIXME: other TourneysPlayers should be added here
|
||||
|
||||
session.flush()
|
||||
|
@ -356,18 +356,20 @@ class HandPlayer(MappedBase):
|
|||
class Site(object):
|
||||
"""Class reflecting Players db table"""
|
||||
INITIAL_DATA = [
|
||||
(1 , 'Full Tilt Poker','USD'),
|
||||
(2 , 'PokerStars', 'USD'),
|
||||
(3 , 'Everleaf', 'USD'),
|
||||
(4 , 'Win2day', 'USD'),
|
||||
(5 , 'OnGame', 'USD'),
|
||||
(6 , 'UltimateBet', 'USD'),
|
||||
(7 , 'Betfair', 'USD'),
|
||||
(8 , 'Absolute', 'USD'),
|
||||
(9 , 'PartyPoker', 'USD'),
|
||||
(10, 'Partouche', 'EUR'),
|
||||
(1 , 'Full Tilt Poker','FT'),
|
||||
(2 , 'PokerStars', 'PS'),
|
||||
(3 , 'Everleaf', 'EV'),
|
||||
(4 , 'Win2day', 'W2'),
|
||||
(5 , 'OnGame', 'OG'),
|
||||
(6 , 'UltimateBet', 'UB'),
|
||||
(7 , 'Betfair', 'BF'),
|
||||
(8 , 'Absolute', 'AB'),
|
||||
(9 , 'PartyPoker', 'PP'),
|
||||
(10, 'Partouche', 'PA'),
|
||||
(11, 'Carbon', 'CA'),
|
||||
(12, 'PKR', 'PK'),
|
||||
]
|
||||
INITIAL_DATA_KEYS = ('id', 'name', 'currency')
|
||||
INITIAL_DATA_KEYS = ('id', 'name', 'code')
|
||||
|
||||
INITIAL_DATA_DICTS = [ dict(zip(INITIAL_DATA_KEYS, datum)) for datum in INITIAL_DATA ]
|
||||
|
||||
|
@ -388,7 +390,7 @@ class Tourney(MappedBase):
|
|||
|
||||
|
||||
class TourneyType(MappedBase):
|
||||
"""Class reflecting TourneysType db table"""
|
||||
"""Class reflecting TourneyType db table"""
|
||||
|
||||
@classmethod
|
||||
def get_or_create(cls, session, **kwargs):
|
||||
|
@ -396,12 +398,12 @@ class TourneyType(MappedBase):
|
|||
|
||||
Required kwargs:
|
||||
buyin fee speed maxSeats knockout
|
||||
rebuyOrAddon headsUp shootout matrix sng
|
||||
rebuy addOn shootout matrix sng currency
|
||||
"""
|
||||
return get_or_create(cls, session, **kwargs)[0]
|
||||
|
||||
|
||||
class TourneyPlayer(MappedBase):
|
||||
class TourneysPlayer(MappedBase):
|
||||
"""Class reflecting TourneysPlayers db table"""
|
||||
|
||||
@classmethod
|
||||
|
@ -453,7 +455,7 @@ mapper (Gametype, gametypes_table, properties={
|
|||
})
|
||||
mapper (Player, players_table, properties={
|
||||
'playerHands': relation(HandPlayer, backref='player'),
|
||||
'playerTourney': relation(TourneyPlayer, backref='player'),
|
||||
'playerTourney': relation(TourneysPlayer, backref='player'),
|
||||
})
|
||||
mapper (Site, sites_table, properties={
|
||||
'gametypes': relation(Gametype, backref = 'site'),
|
||||
|
@ -471,7 +473,7 @@ mapper (Tourney, tourneys_table)
|
|||
mapper (TourneyType, tourney_types_table, properties={
|
||||
'tourneys': relation(Tourney, backref='type'),
|
||||
})
|
||||
mapper (TourneyPlayer, tourneys_players_table)
|
||||
mapper (TourneysPlayer, tourneys_players_table)
|
||||
|
||||
class LambdaKeyDict(defaultdict):
|
||||
"""Operates like defaultdict but passes key argument to the factory function"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Grigorij Indigirkin
|
||||
|
@ -45,6 +45,7 @@ autorates_table = Table('Autorates', metadata,
|
|||
gametypes_table = Table('Gametypes', metadata,
|
||||
Column('id', SmallInteger, primary_key=True),
|
||||
Column('siteId', SmallInteger, ForeignKey("Sites.id"), nullable=False), # SMALLINT
|
||||
Column('currency', String(4), nullable=False), # varchar(4) NOT NULL
|
||||
Column('type', String(4), nullable=False), # char(4) NOT NULL
|
||||
Column('base', String(4), nullable=False), # char(4) NOT NULL
|
||||
Column('category', String(9), nullable=False), # varchar(9) NOT NULL
|
||||
|
@ -64,7 +65,7 @@ hands_table = Table('Hands', metadata,
|
|||
Column('tableName', String(30), nullable=False),
|
||||
Column('siteHandNo', BigIntColumn, nullable=False),
|
||||
Column('gametypeId', SmallInteger, ForeignKey('Gametypes.id'), nullable=False),
|
||||
Column('handStart', DateTime, nullable=False),
|
||||
Column('startTime', DateTime, nullable=False),
|
||||
Column('importTime', DateTime, nullable=False),
|
||||
Column('seats', SmallInteger, nullable=False),
|
||||
Column('maxSeats', SmallInteger, nullable=False),
|
||||
|
@ -354,7 +355,7 @@ settings_table = Table('Settings', metadata,
|
|||
sites_table = Table('Sites', metadata,
|
||||
Column('id', SmallInteger, primary_key=True),
|
||||
Column('name', String(32), nullable=False), # varchar(32) NOT NULL
|
||||
Column('currency', String(3), nullable=False), # char(3) NOT NULL
|
||||
Column('code', String(2), nullable=False), # char(2) NOT NULL
|
||||
mysql_charset='utf8',
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
|
@ -368,17 +369,11 @@ tourneys_table = Table('Tourneys', metadata,
|
|||
Column('prizepool', Integer), # INT NOT NULL
|
||||
Column('tourStartTime', DateTime), # DATETIME NOT NULL
|
||||
Column('tourEndTime', DateTime), # DATETIME
|
||||
Column('buyinChips', Integer), # INT
|
||||
Column('tourneyName', String(40)), # varchar(40)
|
||||
# Mask use : 1=Positionnal Winnings|2=Match1|4=Match2|...|pow(2,n)=Matchn
|
||||
Column('matrixIdProcessed',SmallInteger, default=0), # TINYINT UNSIGNED DEFAULT 0
|
||||
Column('rebuyChips', Integer, default=0), # INT DEFAULT 0
|
||||
Column('addonChips', Integer, default=0), # INT DEFAULT 0
|
||||
Column('rebuyAmount', MoneyColumn, default=0), # INT DEFAULT 0
|
||||
Column('addonAmount', MoneyColumn, default=0), # INT DEFAULT 0
|
||||
Column('totalRebuys', Integer, default=0), # INT DEFAULT 0
|
||||
Column('totalAddons', Integer, default=0), # INT DEFAULT 0
|
||||
Column('koBounty', Integer, default=0), # INT DEFAULT 0
|
||||
Column('totalRebuyCount', Integer, default=0), # INT DEFAULT 0
|
||||
Column('totalAddOnCount', Integer, default=0), # INT DEFAULT 0
|
||||
Column('comment', Text), # TEXT
|
||||
Column('commentTs', DateTime), # DATETIME
|
||||
mysql_charset='utf8',
|
||||
|
@ -390,36 +385,46 @@ Index('siteTourneyNo', tourneys_table.c.siteTourneyNo, tourneys_table.c.tourneyT
|
|||
tourney_types_table = Table('TourneyTypes', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('siteId', SmallInteger, ForeignKey("Sites.id"), nullable=False),
|
||||
Column('currency', String(4), nullable=False), # varchar(4) NOT NULL
|
||||
Column('buyin', Integer, nullable=False), # INT NOT NULL
|
||||
Column('fee', Integer, nullable=False, default=0), # INT NOT NULL
|
||||
Column('fee', Integer, nullable=False), # INT NOT NULL
|
||||
Column('buyInChips', Integer, nullable=False), # INT NOT NULL
|
||||
Column('maxSeats', Boolean, nullable=False, default=-1), # INT NOT NULL DEFAULT -1
|
||||
Column('rebuy', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('rebuyCost', Integer), # INT
|
||||
Column('rebuyChips', Integer), # INT
|
||||
Column('addOn', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('addOnCost', Integer), # INT
|
||||
Column('addOnChips', Integer), # INT
|
||||
Column('knockout', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('rebuyOrAddon', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('koBounty', Integer), # INT
|
||||
Column('speed', String(10)), # varchar(10)
|
||||
Column('headsUp', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('shootout', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('matrix', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('sng', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('satellite', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('doubleOrNothing', Boolean, nullable=False, default=False), # BOOLEAN NOT NULL DEFAULT False
|
||||
Column('guarantee', Integer, nullable=False, default=0), # INT NOT NULL DEFAULT 0
|
||||
mysql_charset='utf8',
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
Index('tourneyTypes_all',
|
||||
tourney_types_table.c.siteId, tourney_types_table.c.buyin, tourney_types_table.c.fee,
|
||||
tourney_types_table.c.maxSeats, tourney_types_table.c.knockout, tourney_types_table.c.rebuyOrAddon,
|
||||
tourney_types_table.c.speed, tourney_types_table.c.headsUp, tourney_types_table.c.shootout,
|
||||
tourney_types_table.c.matrix, tourney_types_table.c.sng)
|
||||
tourney_types_table.c.maxSeats, tourney_types_table.c.knockout, tourney_types_table.c.rebuy,
|
||||
tourney_types_table.c.addOn, tourney_types_table.c.speed,
|
||||
tourney_types_table.c.shootout, tourney_types_table.c.matrix, tourney_types_table.c.sng)
|
||||
|
||||
|
||||
tourneys_players_table = Table('TourneysPlayers', metadata,
|
||||
Column('id', BigIntColumn, primary_key=True),
|
||||
Column('tourneyId', Integer, ForeignKey("Tourneys.id"), nullable=False),
|
||||
Column('playerId', Integer, ForeignKey("Players.id"), nullable=False),
|
||||
Column('payinAmount', Integer), # INT NOT NULL
|
||||
Column('rank', Integer), # INT NOT NULL
|
||||
Column('winnings', Integer), # INT NOT NULL
|
||||
Column('nbRebuys', Integer, default=0), # INT DEFAULT 0
|
||||
Column('nbAddons', Integer, default=0), # INT DEFAULT 0
|
||||
Column('nbKO', Integer, default=0), # INT DEFAULT 0
|
||||
Column('winningsCurrency', Text), # TEXT
|
||||
Column('rebuyCount', Integer, default=0), # INT DEFAULT 0
|
||||
Column('addOnCount', Integer, default=0), # INT DEFAULT 0
|
||||
Column('koCount', Integer, default=0), # INT DEFAULT 0
|
||||
Column('comment', Text), # TEXT
|
||||
Column('commentTs', DateTime), # DATETIME
|
||||
mysql_charset='utf8',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Carl Gherardi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -105,7 +105,7 @@ class Betfair(HandHistoryConverter):
|
|||
logging.debug("HID %s, Table %s" % (m.group('HID'), m.group('TABLE')))
|
||||
hand.handid = m.group('HID')
|
||||
hand.tablename = m.group('TABLE')
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%A, %B %d, %H:%M:%S GMT %Y")
|
||||
hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), "%A, %B %d, %H:%M:%S GMT %Y")
|
||||
#hand.buttonpos = int(m.group('BUTTON'))
|
||||
|
||||
def readPlayerStacks(self, hand):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2010, Matthew Boss
|
||||
|
@ -156,7 +156,7 @@ or None if we fail to get the info """
|
|||
hand.handid = m.group('HID1') + m.group('HID2')
|
||||
hand.tablename = m.group('TABLE')[:-1]
|
||||
hand.maxseats = 2 # This value may be increased as necessary
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME')[:12],
|
||||
hand.startTime = datetime.datetime.strptime(m.group('DATETIME')[:12],
|
||||
'%Y%m%d%H%M')
|
||||
# Check that the hand is complete up to the awarding of the pot; if
|
||||
# not, the hand is unparseable
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2010 Mika Bostrom
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Configuration.py
|
||||
|
||||
|
@ -434,6 +434,21 @@ class Import:
|
|||
return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \
|
||||
% (self.interval, self.callFpdbHud, self.hhArchiveBase, self.saveActions, self.fastStoreHudCache)
|
||||
|
||||
class Email:
|
||||
def __init__(self, node):
|
||||
self.node = node
|
||||
self.host= node.getAttribute("host")
|
||||
self.username = node.getAttribute("username")
|
||||
self.password = node.getAttribute("password")
|
||||
self.useSsl = node.getAttribute("useSsl")
|
||||
self.folder = node.getAttribute("folder")
|
||||
self.siteName = node.getAttribute("siteName")
|
||||
self.fetchType = node.getAttribute("fetchType")
|
||||
|
||||
def __str__(self):
|
||||
return " host = %s\n username = %s\n password = %s\n useSsl = %s\n folder = %s\n" \
|
||||
% (self.host, self.username, self.password, self.useSsl, self.folder)
|
||||
|
||||
class HudUI:
|
||||
def __init__(self, node):
|
||||
self.node = node
|
||||
|
@ -593,6 +608,10 @@ class Config:
|
|||
imp = Import(node = imp_node)
|
||||
self.imp = imp
|
||||
|
||||
for email_node in doc.getElementsByTagName("email"):
|
||||
email = Email(node = email_node)
|
||||
self.email = email
|
||||
|
||||
for hui_node in doc.getElementsByTagName('hud_ui'):
|
||||
hui = HudUI(node = hui_node)
|
||||
self.ui = hui
|
||||
|
@ -638,6 +657,14 @@ class Config:
|
|||
if site_node.getAttribute("site_name") == site:
|
||||
return site_node
|
||||
|
||||
def getGameNode(self,gameName):
|
||||
"""returns DOM game node for a given game"""
|
||||
for gameNode in self.doc.getElementsByTagName("game"):
|
||||
#print "getGameNode gameNode:",gameNode
|
||||
if gameNode.getAttribute("game_name") == gameName:
|
||||
return gameNode
|
||||
#end def getGameNode
|
||||
|
||||
def get_aux_node(self, aux):
|
||||
for aux_node in self.doc.getElementsByTagName("aw"):
|
||||
if aux_node.getAttribute("name") == aux:
|
||||
|
@ -717,6 +744,49 @@ class Config:
|
|||
location_node.setAttribute("y", str( locations[i-1][1] ))
|
||||
self.supported_sites[site_name].layout[max].location[i] = ( locations[i-1][0], locations[i-1][1] )
|
||||
|
||||
def editStats(self, gameName, statArray):
|
||||
"""replaces stat selection for the given gameName with the given statArray"""
|
||||
gameNode = self.getGameNode(gameName)
|
||||
statNodes = gameNode.getElementsByTagName("stat")
|
||||
|
||||
for node in statNodes:
|
||||
gameNode.removeChild(node)
|
||||
|
||||
gameNode.setAttribute("rows", str(len(statArray)))
|
||||
gameNode.setAttribute("cols", str(len(statArray[0])))
|
||||
|
||||
for rowNumber in range(len(statArray)):
|
||||
for columnNumber in range(len(statArray[rowNumber])):
|
||||
newStat=self.doc.createElement("stat")
|
||||
|
||||
newAttrStatName=self.doc.createAttribute("stat_name")
|
||||
newStat.setAttributeNode(newAttrStatName)
|
||||
newStat.setAttribute("stat_name", statArray[rowNumber][columnNumber])
|
||||
|
||||
newAttrStatName=self.doc.createAttribute("row")
|
||||
newStat.setAttributeNode(newAttrStatName)
|
||||
newStat.setAttribute("row", str(rowNumber))
|
||||
|
||||
newAttrStatName=self.doc.createAttribute("col")
|
||||
newStat.setAttributeNode(newAttrStatName)
|
||||
newStat.setAttribute("col", str(columnNumber))
|
||||
|
||||
newAttrStatName=self.doc.createAttribute("click")
|
||||
newStat.setAttributeNode(newAttrStatName)
|
||||
newStat.setAttribute("click", "tog_decorate")
|
||||
|
||||
newAttrStatName=self.doc.createAttribute("popup")
|
||||
newStat.setAttributeNode(newAttrStatName)
|
||||
newStat.setAttribute("popup", "default")
|
||||
|
||||
newAttrStatName=self.doc.createAttribute("tip")
|
||||
newStat.setAttributeNode(newAttrStatName)
|
||||
newStat.setAttribute("tip", "tip1")
|
||||
|
||||
gameNode.appendChild(newStat)
|
||||
statNodes = gameNode.getElementsByTagName("stat")
|
||||
#end def editStats
|
||||
|
||||
def edit_aux_layout(self, aux_name, max, width = None, height = None, locations = None):
|
||||
aux_node = self.get_aux_node(aux_name)
|
||||
layout_node = self.get_layout_node(aux_node, max)
|
||||
|
@ -1052,8 +1122,8 @@ class Config:
|
|||
def get_supported_games(self):
|
||||
"""Get the list of supported games."""
|
||||
sg = []
|
||||
for game in c.supported_games.keys():
|
||||
sg.append(c.supported_games[game].game_name)
|
||||
for game in self.supported_games.keys():
|
||||
sg.append(self.supported_games[game].game_name)
|
||||
return sg
|
||||
|
||||
def execution_path(self, filename):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Database.py
|
||||
|
||||
|
@ -53,7 +53,6 @@ log = logging.getLogger("db")
|
|||
# FreePokerTools modules
|
||||
import SQL
|
||||
import Card
|
||||
import Tourney
|
||||
import Charset
|
||||
from Exceptions import *
|
||||
import Configuration
|
||||
|
@ -75,7 +74,7 @@ except ImportError:
|
|||
use_numpy = False
|
||||
|
||||
|
||||
DB_VERSION = 119
|
||||
DB_VERSION = 136
|
||||
|
||||
|
||||
# Variance created as sqlite has a bunch of undefined aggregate functions.
|
||||
|
@ -141,6 +140,8 @@ class Database:
|
|||
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
|
||||
#, {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0} unique indexes not dropped
|
||||
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
|
||||
, {'tab':'Backings', 'col':'tourneysPlayerId', 'drop':0}
|
||||
, {'tab':'Backings', 'col':'playerId', 'drop':0}
|
||||
]
|
||||
, [ # indexes for sqlite (list index 4)
|
||||
{'tab':'Hands', 'col':'gametypeId', 'drop':0}
|
||||
|
@ -155,6 +156,8 @@ class Database:
|
|||
, {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1}
|
||||
, {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0}
|
||||
, {'tab':'TourneyTypes', 'col':'siteId', 'drop':0}
|
||||
, {'tab':'Backings', 'col':'tourneysPlayerId', 'drop':0}
|
||||
, {'tab':'Backings', 'col':'playerId', 'drop':0}
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -291,6 +294,30 @@ class Database:
|
|||
self.connection.rollback() # make sure any locks taken so far are released
|
||||
#end def __init__
|
||||
|
||||
def dumpDatabase(self):
|
||||
result="fpdb database dump\nDB version=" + str(DB_VERSION)+"\n\n"
|
||||
|
||||
tables=self.cursor.execute(self.sql.query['list_tables'])
|
||||
tables=self.cursor.fetchall()
|
||||
for table in tables:
|
||||
table=table[0]
|
||||
|
||||
print "table:", table
|
||||
result+="###################\nTable "+table+"\n###################\n"
|
||||
rows=self.cursor.execute(self.sql.query['get'+table])
|
||||
rows=self.cursor.fetchall()
|
||||
columnNames=self.cursor.description
|
||||
if not rows:
|
||||
result+="empty table\n"
|
||||
else:
|
||||
for row in rows:
|
||||
for columnNumber in range(len(columnNames)):
|
||||
result+=(" "+columnNames[columnNumber][0]+"="+str(row[columnNumber])+"\n")
|
||||
result+="\n"
|
||||
result+="\n"
|
||||
return result
|
||||
#end def dumpDatabase
|
||||
|
||||
# could be used by hud to change hud style
|
||||
def set_hud_style(self, style):
|
||||
self.hud_style = style
|
||||
|
@ -490,6 +517,7 @@ class Database:
|
|||
self.connection.commit()
|
||||
self.cursor.close()
|
||||
self.connection.close()
|
||||
self.__connected = False
|
||||
|
||||
def reconnect(self, due_to_error=False):
|
||||
"""Reconnects the DB"""
|
||||
|
@ -554,6 +582,24 @@ class Database:
|
|||
c.execute(self.sql.query['get_hand_info'], new_hand_id)
|
||||
return c.fetchall()
|
||||
|
||||
def getHandCount(self):
|
||||
c = self.connection.cursor()
|
||||
c.execute(self.sql.query['getHandCount'])
|
||||
return c.fetchone()[0]
|
||||
#end def getHandCount
|
||||
|
||||
def getTourneyCount(self):
|
||||
c = self.connection.cursor()
|
||||
c.execute(self.sql.query['getTourneyCount'])
|
||||
return c.fetchone()[0]
|
||||
#end def getTourneyCount
|
||||
|
||||
def getTourneyTypeCount(self):
|
||||
c = self.connection.cursor()
|
||||
c.execute(self.sql.query['getTourneyTypeCount'])
|
||||
return c.fetchone()[0]
|
||||
#end def getTourneyCount
|
||||
|
||||
def get_actual_seat(self, hand_id, name):
|
||||
c = self.connection.cursor()
|
||||
c.execute(self.sql.query['get_actual_seat'], (hand_id, name))
|
||||
|
@ -803,17 +849,18 @@ class Database:
|
|||
#print "session stat_dict =", stat_dict
|
||||
#return stat_dict
|
||||
|
||||
def get_player_id(self, config, site, player_name):
|
||||
def get_player_id(self, config, siteName, playerName):
|
||||
c = self.connection.cursor()
|
||||
#print "get_player_id: player_name =", player_name, type(player_name)
|
||||
p_name = Charset.to_utf8(player_name)
|
||||
c.execute(self.sql.query['get_player_id'], (p_name, site))
|
||||
siteNameUtf = Charset.to_utf8(siteName)
|
||||
playerNameUtf = unicode(playerName)
|
||||
#print "db.get_player_id siteName",siteName,"playerName",playerName
|
||||
c.execute(self.sql.query['get_player_id'], (playerNameUtf, siteNameUtf))
|
||||
row = c.fetchone()
|
||||
if row:
|
||||
return row[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_player_names(self, config, site_id=None, like_player_name="%"):
|
||||
"""Fetch player names from players. Use site_id and like_player_name if provided"""
|
||||
|
||||
|
@ -1063,6 +1110,7 @@ class Database:
|
|||
"""(Re-)creates the tables of the current DB"""
|
||||
|
||||
self.drop_tables()
|
||||
self.resetPlayerIDs()
|
||||
self.create_tables()
|
||||
self.createAllIndexes()
|
||||
self.commit()
|
||||
|
@ -1089,6 +1137,7 @@ class Database:
|
|||
c.execute(self.sql.query['createHandsPlayersTable'])
|
||||
c.execute(self.sql.query['createHandsActionsTable'])
|
||||
c.execute(self.sql.query['createHudCacheTable'])
|
||||
c.execute(self.sql.query['createBackingsTable'])
|
||||
|
||||
# Create unique indexes:
|
||||
log.debug("Creating unique indexes")
|
||||
|
@ -1166,7 +1215,7 @@ class Database:
|
|||
for idx in self.indexes[self.backend]:
|
||||
if self.backend == self.MYSQL_INNODB:
|
||||
print "Creating mysql index %s %s" %(idx['tab'], idx['col'])
|
||||
log.debug("Creating sqlite index %s %s" %(idx['tab'], idx['col']))
|
||||
log.debug("Creating mysql index %s %s" %(idx['tab'], idx['col']))
|
||||
try:
|
||||
s = "create index %s on %s(%s)" % (idx['col'],idx['tab'],idx['col'])
|
||||
self.get_cursor().execute(s)
|
||||
|
@ -1174,8 +1223,8 @@ class Database:
|
|||
print " create idx failed: " + str(sys.exc_info())
|
||||
elif self.backend == self.PGSQL:
|
||||
# mod to use tab_col for index name?
|
||||
print "Creating pg index %s %s" %(idx['tab'], idx['col'])
|
||||
log.debug("Creating sqlite index %s %s" %(idx['tab'], idx['col']))
|
||||
print "Creating pgsql index %s %s" %(idx['tab'], idx['col'])
|
||||
log.debug("Creating pgsql index %s %s" %(idx['tab'], idx['col']))
|
||||
try:
|
||||
s = "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col'])
|
||||
self.get_cursor().execute(s)
|
||||
|
@ -1344,29 +1393,18 @@ class Database:
|
|||
def fillDefaultData(self):
|
||||
c = self.get_cursor()
|
||||
c.execute("INSERT INTO Settings (version) VALUES (%s);" % (DB_VERSION))
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Win2day', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('OnGame', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('UltimateBet', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Betfair', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Partouche', 'EUR')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')")
|
||||
c.execute("INSERT INTO Sites (name,currency) VALUES ('PKR', 'USD')")
|
||||
if self.backend == self.SQLITE:
|
||||
c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
|
||||
elif self.backend == self.PGSQL:
|
||||
c.execute("""insert into TourneyTypes(siteId, buyin, fee, maxSeats, knockout
|
||||
,rebuyOrAddon, speed, headsUp, shootout, matrix)
|
||||
values (1, 0, 0, 0, False, False, null, False, False, False);""")
|
||||
elif self.backend == self.MYSQL_INNODB:
|
||||
c.execute("""insert into TourneyTypes(id, siteId, buyin, fee, maxSeats, knockout
|
||||
,rebuyOrAddon, speed, headsUp, shootout, matrix)
|
||||
values (DEFAULT, 1, 0, 0, 0, False, False, null, False, False, False);""")
|
||||
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Full Tilt Poker', 'FT')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('PokerStars', 'PS')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Everleaf', 'EV')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Win2day', 'W2')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('OnGame', 'OG')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('UltimateBet', 'UB')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Betfair', 'BF')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Absolute', 'AB')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('PartyPoker', 'PP')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Partouche', 'PA')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('Carbon', 'CA')")
|
||||
c.execute("INSERT INTO Sites (name,code) VALUES ('PKR', 'PK')")
|
||||
#end def fillDefaultData
|
||||
|
||||
def rebuild_indexes(self, start=None):
|
||||
|
@ -1374,6 +1412,7 @@ class Database:
|
|||
self.createAllIndexes()
|
||||
self.dropAllForeignKeys()
|
||||
self.createAllForeignKeys()
|
||||
#end def rebuild_indexes
|
||||
|
||||
def rebuild_hudcache(self, h_start=None, v_start=None):
|
||||
"""clears hudcache and rebuilds from the individual handsplayers records"""
|
||||
|
@ -1403,9 +1442,9 @@ class Database:
|
|||
where = ""
|
||||
else:
|
||||
where = "where ( hp.playerId not in " + str(tuple(self.hero_ids.values())) \
|
||||
+ " and h.handStart > '" + v_start + "')" \
|
||||
+ " and h.startTime > '" + v_start + "')" \
|
||||
+ " or ( hp.playerId in " + str(tuple(self.hero_ids.values())) \
|
||||
+ " and h.handStart > '" + h_start + "')"
|
||||
+ " and h.startTime > '" + h_start + "')"
|
||||
rebuild_sql = self.sql.query['rebuildHudCache'].replace('<where_clause>', where)
|
||||
|
||||
self.get_cursor().execute(self.sql.query['clearHudCache'])
|
||||
|
@ -1520,9 +1559,9 @@ class Database:
|
|||
p['tableName'],
|
||||
p['gameTypeId'],
|
||||
p['siteHandNo'],
|
||||
0, # tourneyId: 0 means not a tourney hand
|
||||
p['handStart'],
|
||||
datetime.today(), #importtime
|
||||
p['tourneyId'],
|
||||
p['startTime'],
|
||||
datetime.utcnow(), #importtime
|
||||
p['seats'],
|
||||
p['maxSeats'],
|
||||
p['texture'],
|
||||
|
@ -1559,6 +1598,7 @@ class Database:
|
|||
pids[p],
|
||||
pdata[p]['startCash'],
|
||||
pdata[p]['seatNo'],
|
||||
pdata[p]['sitout'],
|
||||
pdata[p]['card1'],
|
||||
pdata[p]['card2'],
|
||||
pdata[p]['card3'],
|
||||
|
@ -1602,6 +1642,7 @@ class Database:
|
|||
pdata[p]['street4Bets'],
|
||||
pdata[p]['position'],
|
||||
pdata[p]['tourneyTypeId'],
|
||||
pdata[p]['tourneysPlayersIds'],
|
||||
pdata[p]['startCards'],
|
||||
pdata[p]['street0_3BChance'],
|
||||
pdata[p]['street0_3BDone'],
|
||||
|
@ -1793,10 +1834,14 @@ class Database:
|
|||
hilo = "s"
|
||||
elif game['category'] in ['razz','27_3draw','badugi']:
|
||||
hilo = "l"
|
||||
tmp = self.insertGameTypes( (siteid, game['type'], game['base'], game['category'], game['limitType'], hilo,
|
||||
tmp = self.insertGameTypes( (siteid, 'USD', game['type'], game['base'], game['category'], game['limitType'], hilo,
|
||||
int(Decimal(game['sb'])*100), int(Decimal(game['bb'])*100), 0, 0) )
|
||||
#FIXME: recognise currency
|
||||
return tmp[0]
|
||||
|
||||
def resetPlayerIDs(self):
|
||||
self.pcache = None
|
||||
|
||||
def getSqlPlayerIDs(self, pnames, siteid):
|
||||
result = {}
|
||||
if(self.pcache == None):
|
||||
|
@ -1929,59 +1974,141 @@ class Database:
|
|||
print "***Error sending finish: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||
# end def send_finish_msg():
|
||||
|
||||
def tRecogniseTourneyType(self, tourney):
|
||||
log.debug("Database.tRecogniseTourneyType")
|
||||
typeId = 1
|
||||
def createTourneyType(self, hand):#note: this method is used on Hand and TourneySummary objects
|
||||
tourneyTypeId = 1
|
||||
|
||||
# Check if Tourney exists, and if so retrieve TTypeId : in that case, check values of the ttype
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute (self.sql.query['getTourneyTypeIdByTourneyNo'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.tourNo, tourney.siteId)
|
||||
(hand.tourNo, hand.siteId)
|
||||
)
|
||||
result=cursor.fetchone()
|
||||
|
||||
expectedValues = { 1 : "buyin", 2 : "fee", 4 : "isKO", 5 : "isRebuy", 6 : "speed",
|
||||
7 : "isHU", 8 : "isShootout", 9 : "isMatrix" }
|
||||
typeIdMatch = True
|
||||
|
||||
try:
|
||||
len(result)
|
||||
typeId = result[0]
|
||||
log.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId)
|
||||
for ev in expectedValues :
|
||||
if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ):
|
||||
log.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) )
|
||||
typeIdMatch = False
|
||||
#break
|
||||
except:
|
||||
# Tourney not found : a TourneyTypeId has to be found or created for that specific tourney
|
||||
typeIdMatch = False
|
||||
|
||||
if typeIdMatch == False :
|
||||
# Check for an existing TTypeId that matches tourney info (buyin/fee, knockout, rebuy, speed, matrix, shootout)
|
||||
# if not found create it
|
||||
log.debug("Searching for a TourneyTypeId matching TourneyType data")
|
||||
if result:
|
||||
tourneyTypeId = result[0]
|
||||
else:
|
||||
# Check for an existing TTypeId that matches tourney info, if not found create it
|
||||
cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO,
|
||||
tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix)
|
||||
(hand.siteId, hand.buyinCurrency, hand.buyin, hand.fee, hand.gametype['category'], hand.gametype['limitType'], hand.isKO,
|
||||
hand.isRebuy, hand.isRebuy, hand.speed, hand.isShootout, hand.isMatrix, hand.added, hand.addedCurrency)
|
||||
)
|
||||
result=cursor.fetchone()
|
||||
|
||||
try:
|
||||
len(result)
|
||||
typeId = result[0]
|
||||
log.debug("Existing Tourney Type Id found : %d" % typeId)
|
||||
tourneyTypeId = result[0]
|
||||
except TypeError: #this means we need to create a new entry
|
||||
log.debug("Tourney Type Id not found : create one")
|
||||
cursor.execute (self.sql.query['insertTourneyTypes'].replace('%s', self.sql.query['placeholder']),
|
||||
(tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy,
|
||||
tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix)
|
||||
cursor.execute (self.sql.query['insertTourneyType'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.siteId, hand.buyinCurrency, hand.buyin, hand.fee, hand.gametype['category'], hand.gametype['limitType'], hand.buyInChips,
|
||||
hand.isKO, hand.isRebuy,
|
||||
hand.isAddOn, hand.speed, hand.isShootout, hand.isMatrix, hand.added, hand.addedCurrency)
|
||||
)
|
||||
typeId = self.get_last_insert_id(cursor)
|
||||
|
||||
return typeId
|
||||
#end def tRecogniseTourneyType
|
||||
|
||||
tourneyTypeId = self.get_last_insert_id(cursor)
|
||||
return tourneyTypeId
|
||||
#end def createTourneyType
|
||||
|
||||
def createOrUpdateTourney(self, hand, source):#note: this method is used on Hand and TourneySummary objects
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute (self.sql.query['getTourneyByTourneyNo'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.siteId, hand.tourNo))
|
||||
columnNames=[desc[0] for desc in cursor.description]
|
||||
result=cursor.fetchone()
|
||||
|
||||
if result != None:
|
||||
expectedValues = ('comment', 'tourneyName', 'matrixIdProcessed', 'totalRebuyCount', 'totalAddOnCount',
|
||||
'prizepool', 'startTime', 'entries', 'commentTs', 'endTime')
|
||||
updateDb=False
|
||||
resultDict = dict(zip(columnNames, result))
|
||||
|
||||
tourneyId = resultDict["id"]
|
||||
if source=="TS":
|
||||
for ev in expectedValues :
|
||||
if getattr(hand, ev)==None and resultDict[ev]!=None:#DB has this value but object doesnt, so update object
|
||||
setattr(hand, ev, resultDict[ev])
|
||||
elif getattr(hand, ev)!=None and resultDict[ev]==None:#object has this value but DB doesnt, so update DB
|
||||
updateDb=True
|
||||
#elif ev=="startTime":
|
||||
# if (resultDict[ev] < hand.startTime):
|
||||
# hand.startTime=resultDict[ev]
|
||||
if updateDb:
|
||||
cursor.execute (self.sql.query['updateTourney'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.entries, hand.prizepool, hand.startTime, hand.endTime, hand.tourneyName,
|
||||
hand.matrixIdProcessed, hand.totalRebuyCount, hand.totalAddOnCount, hand.comment, hand.commentTs, tourneyId))
|
||||
else:
|
||||
if source=="HHC":
|
||||
cursor.execute (self.sql.query['insertTourney'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.tourneyTypeId, hand.tourNo, None, None,
|
||||
hand.startTime, None, None, None, None, None))
|
||||
elif source=="TS":
|
||||
cursor.execute (self.sql.query['insertTourney'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.tourneyTypeId, hand.tourNo, hand.entries, hand.prizepool, hand.startTime,
|
||||
hand.endTime, hand.tourneyName, hand.matrixIdProcessed, hand.totalRebuyCount, hand.totalAddOnCount))
|
||||
else:
|
||||
raise FpdbParseError("invalid source in Database.createOrUpdateTourney")
|
||||
tourneyId = self.get_last_insert_id(cursor)
|
||||
return tourneyId
|
||||
#end def createOrUpdateTourney
|
||||
|
||||
def createOrUpdateTourneysPlayers(self, hand, source):#note: this method is used on Hand and TourneySummary objects
|
||||
tourneysPlayersIds={}
|
||||
for player in hand.players:
|
||||
if source=="TS": #TODO remove this horrible hack
|
||||
playerId = hand.dbid_pids[player]
|
||||
elif source=="HHC":
|
||||
playerId = hand.dbid_pids[player[1]]
|
||||
else:
|
||||
raise FpdbParseError("invalid source in Database.createOrUpdateTourneysPlayers")
|
||||
|
||||
cursor = self.get_cursor()
|
||||
cursor.execute (self.sql.query['getTourneysPlayersByIds'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.tourneyId, playerId))
|
||||
columnNames=[desc[0] for desc in cursor.description]
|
||||
result=cursor.fetchone()
|
||||
|
||||
if result != None:
|
||||
expectedValues = ('rank', 'winnings', 'winningsCurrency', 'rebuyCount', 'addOnCount', 'koCount')
|
||||
updateDb=False
|
||||
resultDict = dict(zip(columnNames, result))
|
||||
|
||||
tourneysPlayersIds[player[1]]=result[0]
|
||||
if source=="TS":
|
||||
for ev in expectedValues :
|
||||
handAttribute=ev
|
||||
if ev!="winnings" and ev!="winningsCurrency":
|
||||
handAttribute+="s"
|
||||
|
||||
if getattr(hand, handAttribute)[player]==None and resultDict[ev]!=None:#DB has this value but object doesnt, so update object
|
||||
setattr(hand, handAttribute, resultDict[ev][player])
|
||||
elif getattr(hand, handAttribute)[player]!=None and resultDict[ev]==None:#object has this value but DB doesnt, so update DB
|
||||
updateDb=True
|
||||
if updateDb:
|
||||
cursor.execute (self.sql.query['updateTourneysPlayer'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.ranks[player], hand.winnings[player], hand.winningsCurrency[player],
|
||||
hand.rebuyCounts[player], hand.addOnCounts[player], hand.koCounts[player], tourneysPlayersIds[player[1]]))
|
||||
else:
|
||||
if source=="HHC":
|
||||
cursor.execute (self.sql.query['insertTourneysPlayer'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.tourneyId, playerId, None, None, None, None, None, None))
|
||||
elif source=="TS":
|
||||
#print "all values: tourneyId",hand.tourneyId, "playerId",playerId, "rank",hand.ranks[player], "winnings",hand.winnings[player], "winCurr",hand.winningsCurrency[player], hand.rebuyCounts[player], hand.addOnCounts[player], hand.koCounts[player]
|
||||
if hand.ranks[player]:
|
||||
cursor.execute (self.sql.query['insertTourneysPlayer'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.tourneyId, playerId, int(hand.ranks[player]), int(hand.winnings[player]), hand.winningsCurrency[player],
|
||||
hand.rebuyCounts[player], hand.addOnCounts[player], hand.koCounts[player]))
|
||||
else:
|
||||
cursor.execute (self.sql.query['insertTourneysPlayer'].replace('%s', self.sql.query['placeholder']),
|
||||
(hand.tourneyId, playerId, None, None, None,
|
||||
hand.rebuyCounts[player], hand.addOnCounts[player], hand.koCounts[player]))
|
||||
tourneysPlayersIds[player[1]]=self.get_last_insert_id(cursor)
|
||||
return tourneysPlayersIds
|
||||
#end def createOrUpdateTourneysPlayers
|
||||
|
||||
def getTourneyTypesIds(self):
|
||||
c = self.connection.cursor()
|
||||
c.execute(self.sql.query['getTourneyTypesIds'])
|
||||
result = c.fetchall()
|
||||
return result
|
||||
#end def getTourneyTypesIds
|
||||
#end class Database
|
||||
|
||||
# Class used to hold all the data needed to write a hand to the db
|
||||
# mainParser() in fpdb_parse_logic.py creates one of these and then passes it to
|
||||
|
@ -2028,12 +2155,11 @@ class HandToWrite:
|
|||
self.maxSeats = None
|
||||
self.tableName = None
|
||||
self.seatNos = None
|
||||
self.payin_amounts = None # tourney import was complaining mightily about this missing
|
||||
except:
|
||||
print "htw.init error: " + str(sys.exc_info())
|
||||
raise
|
||||
# end def __init__
|
||||
|
||||
|
||||
def set_all( self, config, settings, base, category, siteTourneyNo, buyin
|
||||
, fee, knockout, entries, prizepool, tourneyStartTime
|
||||
, isTourney, tourneyTypeId, siteID, siteHandNo
|
||||
|
|
|
@ -1,827 +0,0 @@
|
|||
#!/usr/bin/python2
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 J. Urner
|
||||
#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.
|
||||
|
||||
"""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 pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
#*******************************************************************************************************
|
||||
class DatabaseManager(gobject.GObject):
|
||||
DatabaseTypes = {}
|
||||
|
||||
@classmethod
|
||||
def from_fpdb(klass, data, defaultDatabaseType=None):
|
||||
|
||||
#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, 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)
|
||||
|
||||
def to_fpdb(self):
|
||||
pass
|
||||
|
||||
|
||||
def __init__(self, databases=None, defaultDatabaseType=None):
|
||||
gobject.GObject.__init__(self)
|
||||
|
||||
self._defaultDatabaseType = defaultDatabaseType
|
||||
self._databases = [] if databases is None else list(databases)
|
||||
self._activeDatabase = None
|
||||
def __iter__(self):
|
||||
return iter(self._databases)
|
||||
def set_default_database_type(self, databaseType):
|
||||
self._defaultDatabaseType = defaultDatabaseType
|
||||
def get_default_database_type(self):
|
||||
return self._defaultDatabaseType
|
||||
def database_from_id(self, idDatabase):
|
||||
for database in self._databases:
|
||||
if idDatabase == self.database_id(database):
|
||||
return database
|
||||
def database_id(self, database):
|
||||
return id(database)
|
||||
def add_database(self, database):
|
||||
if database in self._databases:
|
||||
raise ValueError('database already registered')
|
||||
self._databases.append(database)
|
||||
def remove_database(self, database):
|
||||
self._databases.remove(database)
|
||||
|
||||
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):
|
||||
def __new__(klass, name, bases, kws):
|
||||
newKlass = type.__new__(klass, name, bases, kws)
|
||||
if newKlass.Type is not None:
|
||||
if newKlass.Type in DatabaseManager.DatabaseTypes:
|
||||
raise ValueError('data base type already registered for: %s' % newKlass.Type)
|
||||
DatabaseManager.DatabaseTypes[newKlass.Type] = newKlass
|
||||
return newKlass
|
||||
|
||||
class DatabaseTypeBase(object):
|
||||
__metaclass__ = DatabaseTypeMeta
|
||||
Type = None
|
||||
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):
|
||||
Type = 'postgresql'
|
||||
@classmethod
|
||||
def display_name(klass):
|
||||
return 'Postgres'
|
||||
def __init__(self, name='', host='localhost', port=5432, user='postgres', password='', database='fpdb'):
|
||||
self.name = name
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
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):
|
||||
Type = 'mysql'
|
||||
@classmethod
|
||||
def display_name(klass):
|
||||
return 'MySql'
|
||||
def __init__(self, name='', host='localhost', port=3306, user='root', password='', database='fpdb'):
|
||||
self.name = name
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
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):
|
||||
Type = 'sqlite'
|
||||
@classmethod
|
||||
def display_name(klass):
|
||||
return 'SqLite'
|
||||
def __init__(self, name='', host='', file='', database='fpdb'):
|
||||
self.name = name
|
||||
self.file = file
|
||||
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?
|
||||
# ..uncomment to remove unsupported database types
|
||||
#try: import psycopg2
|
||||
#except ImportError: del DatabaseManager.DatabaseTypes['postgres']
|
||||
#try: import MySQLdb
|
||||
#except ImportError: del DatabaseManager.DatabaseTypes['mysql']
|
||||
#try: import sqlite3
|
||||
#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?
|
||||
class WidgetDatabaseProperties(gtk.VBox):
|
||||
|
||||
ModeNew = 0
|
||||
ModeEdit = 1
|
||||
ModeAdd = 2
|
||||
|
||||
class SqLiteFileChooserButton(gtk.HBox):
|
||||
#NOTE: for some weird reason it is impossible to let the user choose a non exiting filename with gtk.FileChooserButton, so impl our own on the fly
|
||||
def __init__(self, widgetDatabaseProperties, parentWidget):
|
||||
gtk.HBox.__init__(self)
|
||||
self.set_homogeneous(False)
|
||||
|
||||
self.parentWidget = parentWidget
|
||||
self.widgetDatabaseProperties = widgetDatabaseProperties
|
||||
self.entry = gtk.Entry()
|
||||
self.button = gtk.Button('...')
|
||||
self.button.connect('clicked', self.on_button_clicked)
|
||||
|
||||
# layout widgets
|
||||
self.pack_start(self.entry, True, True)
|
||||
self.pack_start(self.button, False, False)
|
||||
|
||||
def get_filename(self):
|
||||
return self.entry.get_text()
|
||||
|
||||
def set_filename(self, name):
|
||||
self.entry.set_text(name)
|
||||
|
||||
def on_button_clicked(self, button):
|
||||
if self.widgetDatabaseProperties.mode == WidgetDatabaseProperties.ModeAdd:
|
||||
action = gtk.FILE_CHOOSER_ACTION_OPEN
|
||||
elif self.widgetDatabaseProperties.mode == WidgetDatabaseProperties.ModeNew:
|
||||
action = gtk.FILE_CHOOSER_ACTION_SAVE
|
||||
else:
|
||||
raise ValueError('unsupported dialog mode')
|
||||
dlg = gtk.FileChooserDialog(
|
||||
title='Choose an exiting database file or type in name of a new one',
|
||||
parent=self.parentWidget,
|
||||
action=action,
|
||||
buttons=(
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_OK,
|
||||
),
|
||||
backend=None
|
||||
)
|
||||
dlg.set_default_response(gtk.RESPONSE_OK)
|
||||
dlg.set_do_overwrite_confirmation(True)
|
||||
if dlg.run() == gtk.RESPONSE_OK:
|
||||
fileName = dlg.get_filename()
|
||||
self.set_filename(fileName)
|
||||
dlg.destroy()
|
||||
|
||||
|
||||
#TODO: bit ugly this thingy. try to find a better way to map database attrs to gtk widgets
|
||||
class FieldWidget(object):
|
||||
def __init__(self, text='', attrDatabase='', widget=None, attrGet=None, attrSet=None, defaultValue=None, canEdit=False, tooltip=''):
|
||||
"""
|
||||
@param canEdit: True if the user can edit the attr in edit mode, False otherwise
|
||||
"""
|
||||
self._label = gtk.Label(text)
|
||||
self._attrDatabase = attrDatabase
|
||||
self._widget = widget
|
||||
self._defaultValue = defaultValue
|
||||
self._attrGetter=None,
|
||||
self._attrGet = attrGet
|
||||
self._attrSet = attrSet
|
||||
self._canEdit = canEdit
|
||||
|
||||
self._label.set_tooltip_text(tooltip)
|
||||
self._widget.set_tooltip_text(tooltip)
|
||||
|
||||
def widget(self):
|
||||
return self._widget
|
||||
def label(self):
|
||||
return self._label
|
||||
def is_sensitive(self, database):
|
||||
return hasattr(database, self._attrDatabase)
|
||||
def can_edit(self):
|
||||
return self._canEdit
|
||||
def set_sensitive(self, flag):
|
||||
self._label.set_sensitive(flag)
|
||||
self._widget.set_sensitive(flag)
|
||||
def value_from_database(self, database):
|
||||
getattr(self._widget, self._attrSet)( getattr(database, self._attrDatabase) )
|
||||
def value_to_database(self, database):
|
||||
setattr(database, self._attrDatabase, getattr(self._widget, self._attrGet)() )
|
||||
def reset_value(self):
|
||||
getattr(self._widget, self._attrSet)(self._defaultValue)
|
||||
|
||||
def __init__(self, databaseManager, database, mode=ModeEdit, parentWidget=None):
|
||||
gtk.VBox.__init__(self)
|
||||
|
||||
self.databaseManager = databaseManager
|
||||
self.database = database
|
||||
self.mode = mode
|
||||
self.parentWidget = parentWidget
|
||||
self.fieldWidgets = (
|
||||
self.FieldWidget(
|
||||
text='Name:',
|
||||
attrDatabase='name',
|
||||
widget=gtk.Entry(),
|
||||
defaultValue='',
|
||||
attrGet='get_text',
|
||||
attrSet='set_text',
|
||||
canEdit=True,
|
||||
tooltip='Any name you like to name the database '
|
||||
),
|
||||
self.FieldWidget(
|
||||
text='File:',
|
||||
attrDatabase='file',
|
||||
widget=self.SqLiteFileChooserButton(self, self.parentWidget),
|
||||
defaultValue='',
|
||||
attrGet='get_filename',
|
||||
attrSet='set_filename',
|
||||
canEdit=False,
|
||||
tooltip='Fully qualified path of the file to hold the database '
|
||||
),
|
||||
self.FieldWidget(
|
||||
text='Host:',
|
||||
attrDatabase='host',
|
||||
widget=gtk.Entry(),
|
||||
defaultValue='',
|
||||
attrGet='get_text',
|
||||
attrSet='set_text',
|
||||
canEdit=False,
|
||||
tooltip='Host the database is located at'
|
||||
),
|
||||
self.FieldWidget(
|
||||
text='Port:',
|
||||
attrDatabase='port',
|
||||
widget=gtk.SpinButton(adjustment=gtk.Adjustment(value=0, lower=0, upper=999999, step_incr=1, page_incr=10) ),
|
||||
defaultValue=0,
|
||||
attrGet='get_value',
|
||||
attrSet='set_value',
|
||||
canEdit=False,
|
||||
tooltip='Port to use to connect to the host'
|
||||
),
|
||||
self.FieldWidget(
|
||||
text='User:',
|
||||
attrDatabase='user',
|
||||
widget=gtk.Entry(),
|
||||
defaultValue='',
|
||||
attrGet='get_text',
|
||||
attrSet='set_text',
|
||||
canEdit=False,
|
||||
tooltip='User name used to login to the host'
|
||||
),
|
||||
self.FieldWidget(
|
||||
text='Pwd:',
|
||||
attrDatabase='password',
|
||||
widget=gtk.Entry(),
|
||||
defaultValue='',
|
||||
attrGet='get_text',
|
||||
attrSet='set_text',
|
||||
canEdit=False,
|
||||
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
|
||||
self.comboType = gtk.ComboBox()
|
||||
listStore= gtk.ListStore(str, str)
|
||||
self.comboType.set_model(listStore)
|
||||
cell = gtk.CellRendererText()
|
||||
self.comboType.pack_start(cell, True)
|
||||
self.comboType.add_attribute(cell, 'text', 0)
|
||||
self.comboType.connect('changed', self.on_combo_type_changed)
|
||||
|
||||
# fill database type combo with available database klasses. we store (databaseDisplayName, databaseType) in our model for later lookup
|
||||
iCurrentDatabase = 0
|
||||
databaseTypes = [(klass.display_name(), klass.Type) for klass in databaseManager.DatabaseTypes.values()]
|
||||
databaseTypes.sort()
|
||||
for i, (databaseDisplayName, databaseType) in enumerate(databaseTypes):
|
||||
listStore.append( (databaseDisplayName, databaseType) )
|
||||
if databaseType == self.database.Type:
|
||||
iCurrentDatabase = i
|
||||
if self.mode == self.ModeEdit or len(databaseTypes) < 2:
|
||||
self.comboType.set_button_sensitivity(gtk.SENSITIVITY_OFF)
|
||||
|
||||
# init and layout field widgets
|
||||
self.pack_start(self.comboType, False, False, 2)
|
||||
table = gtk.Table(rows=len(self.fieldWidgets) +1, columns=2, homogeneous=False)
|
||||
self.pack_start(table, False, False, 2)
|
||||
for i,fieldWidget in enumerate(self.fieldWidgets):
|
||||
table.attach(fieldWidget.label(), 0, 1, i, i+1, xoptions=gtk.FILL)
|
||||
table.attach(fieldWidget.widget(), 1, 2, i, i+1)
|
||||
|
||||
# init widget
|
||||
self.comboType.set_active(iCurrentDatabase)
|
||||
self._adjust_widgets(self.database)
|
||||
|
||||
def _adjust_widgets(self, database):
|
||||
for fieldWidget in self.fieldWidgets:
|
||||
isSensitive = fieldWidget.is_sensitive(database)
|
||||
if isSensitive:
|
||||
fieldWidget.value_from_database(database)
|
||||
else:
|
||||
fieldWidget.reset_value()
|
||||
if self.mode == self.ModeEdit:
|
||||
isSensitive = isSensitive and fieldWidget.can_edit()
|
||||
fieldWidget.set_sensitive(isSensitive)
|
||||
|
||||
|
||||
def on_combo_type_changed(self, combo):
|
||||
i = self.comboType.get_active()
|
||||
if i < 0:
|
||||
return
|
||||
|
||||
# check if we need to init a new database
|
||||
currentDatabaseType = self.comboType.get_model()[i][1]
|
||||
if currentDatabaseType == self.database.Type:
|
||||
return
|
||||
|
||||
# create new empty database
|
||||
#NOTE: we dont register it in DatabaseManager
|
||||
self.database = self.databaseManager.DatabaseTypes[currentDatabaseType]()
|
||||
self._adjust_widgets(self.database)
|
||||
|
||||
|
||||
def get_database(self):
|
||||
for fieldWidget in self.fieldWidgets:
|
||||
if fieldWidget.is_sensitive(self.database):
|
||||
fieldWidget.value_to_database(self.database)
|
||||
return self.database
|
||||
|
||||
|
||||
class DialogDatabaseProperties(gtk.Dialog):
|
||||
def __init__(self, databaseManager, database, parent=None, mode=WidgetDatabaseProperties.ModeEdit, title=''):
|
||||
gtk.Dialog.__init__(self,
|
||||
title=title,
|
||||
parent=parent,
|
||||
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
buttons=(
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
)
|
||||
)
|
||||
self.connect('response', self.on_dialog_response)
|
||||
|
||||
# setup widget
|
||||
self.widgetDatabaseProperties = WidgetDatabaseProperties(databaseManager,database, mode=mode, parentWidget=self)
|
||||
self.vbox.pack_start(self.widgetDatabaseProperties, True, True)
|
||||
self.show_all()
|
||||
|
||||
def get_widget_database_properties(self):
|
||||
return self.widgetDatabaseProperties
|
||||
|
||||
def on_dialog_response(self, dlg, responseId):
|
||||
if responseId == gtk.RESPONSE_REJECT:
|
||||
pass
|
||||
elif responseId == gtk.RESPONSE_ACCEPT:
|
||||
pass
|
||||
|
||||
|
||||
#TODO: derrive from gtk.VBox?
|
||||
# ..is there a way to derrive from gtk.Widget or similar? this would make parentWidget kw obsolete
|
||||
class WidgetDatabaseManager(gtk.VBox):
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, databaseManager, parentWidget=None):
|
||||
gtk.VBox.__init__(self)
|
||||
|
||||
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
|
||||
self.labelInfo = gtk.Label('database management')
|
||||
self.labelInfo.set_line_wrap(True)
|
||||
self.labelInfo.set_selectable(True)
|
||||
self.labelInfo.set_single_line_mode(False)
|
||||
self.labelInfo.set_alignment(0, 0)
|
||||
|
||||
# database management buttons
|
||||
|
||||
#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
|
||||
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.set_tooltip_text('creates a new database')
|
||||
self.buttonDatabaseNew.connect('clicked', self.on_button_database_new_clicked)
|
||||
self.buttonDatabaseAdd = gtk.Button("Add..")
|
||||
self.buttonDatabaseAdd.set_tooltip_text('adds an existing database')
|
||||
self.buttonDatabaseAdd.connect('clicked', self.on_button_database_add_clicked)
|
||||
self.buttonDatabaseEdit = gtk.Button("Edit..")
|
||||
self.buttonDatabaseEdit.set_tooltip_text('edit database settings')
|
||||
self.buttonDatabaseEdit.connect('clicked', self.on_button_database_edit_clicked)
|
||||
self.buttonDatabaseEdit.set_sensitive(False)
|
||||
self.buttonDatabaseRemove = gtk.Button("Remove")
|
||||
self.buttonDatabaseRemove.set_tooltip_text('removes the database from the list')
|
||||
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
|
||||
#self.buttonDatabaseDelete = gtk.Button("Delete")
|
||||
#self.buttonDatabaseDelete.set_tooltip_text('removes the database from the list and deletes it')
|
||||
#self.buttonDatabaseDelete.set_sensitive(False)
|
||||
|
||||
# init database tree
|
||||
self.treeDatabases = gtk.TreeView()
|
||||
treeDatabaseColumns = ( # name, displayName, dataType
|
||||
('name', 'Name', str),
|
||||
('status', 'Status', str),
|
||||
('type', 'Type', str),
|
||||
('_id', '', int),
|
||||
)
|
||||
self.treeDatabaseColumns = {} # name --> index
|
||||
store = gtk.ListStore( *[i[2] for i in treeDatabaseColumns] )
|
||||
self.treeDatabases.set_model(store)
|
||||
for i, (name, displayName, dataType) in enumerate(treeDatabaseColumns):
|
||||
col = gtk.TreeViewColumn(displayName, gtk.CellRendererText(), text=i)
|
||||
self.treeDatabases.append_column(col)
|
||||
if name.startswith('_'):
|
||||
col.set_visible(False)
|
||||
self.treeDatabaseColumns[name] = i
|
||||
self.treeDatabases.get_selection().connect('changed', self.on_tree_databases_selection_changed)
|
||||
|
||||
# layout widgets
|
||||
vbox = gtk.VBox(self)
|
||||
vbox.pack_start(self.labelInfo, False, False, 2)
|
||||
vbox.pack_start(gtk.HSeparator(), False, False, 2)
|
||||
hbox = gtk.HBox()
|
||||
self.add(hbox)
|
||||
hbox.set_homogeneous(False)
|
||||
vbox = gtk.VBox()
|
||||
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.buttonDatabaseAdd, False, False, 2)
|
||||
vbox.pack_start(self.buttonDatabaseEdit, False, False, 2)
|
||||
vbox.pack_start(self.buttonDatabaseRemove, False, False, 2)
|
||||
#vbox.pack_start(self.buttonDatabaseDelete, False, False, 2)
|
||||
box = gtk.VBox()
|
||||
vbox.pack_start(box, True, True, 0)
|
||||
|
||||
hbox.pack_start(gtk.VSeparator(), False, False, 2)
|
||||
hbox.pack_end(self.treeDatabases, True, True, 2)
|
||||
|
||||
self.show_all()
|
||||
|
||||
# init widget
|
||||
model = self.treeDatabases.get_model()
|
||||
for database in self.databaseManager:
|
||||
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
|
||||
def on_button_database_new_clicked(self, button):
|
||||
databaseKlass = self.databaseManager.get_default_database_type()
|
||||
if databaseKlass is None:
|
||||
raise ValueError('no default database type set')
|
||||
database = databaseKlass()
|
||||
|
||||
while True:
|
||||
dlg = DialogDatabaseProperties(
|
||||
self.databaseManager,
|
||||
database,
|
||||
parent=self.parentWidget,
|
||||
mode=WidgetDatabaseProperties.ModeNew,
|
||||
title='New database'
|
||||
)
|
||||
response = dlg.run()
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
database = dlg.get_widget_database_properties().get_database()
|
||||
#TODO: initing may or may not take a while. how to handle?
|
||||
error = database.init_new_database()
|
||||
if error:
|
||||
DialogError(parent=dlg, msg=error)
|
||||
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):
|
||||
databaseKlass = self.databaseManager.get_default_database_type()
|
||||
if databaseKlass is None:
|
||||
raise ValueError('no defult database type set')
|
||||
database = databaseKlass()
|
||||
|
||||
while True:
|
||||
dlg = DialogDatabaseProperties(
|
||||
self.databaseManager,
|
||||
database,
|
||||
parent=self.parentWidget,
|
||||
mode=WidgetDatabaseProperties.ModeAdd,
|
||||
title='Add database'
|
||||
)
|
||||
response = dlg.run()
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
database = dlg.get_widget_database_properties().get_database()
|
||||
#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)
|
||||
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()
|
||||
|
||||
def on_button_database_edit_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)
|
||||
dlg = DialogDatabaseProperties(
|
||||
self.databaseManager,
|
||||
database,
|
||||
parent=self.parentWidget,
|
||||
mode=WidgetDatabaseProperties.ModeEdit,
|
||||
title='Edit database'
|
||||
)
|
||||
response = dlg.run()
|
||||
if response == gtk.RESPONSE_REJECT:
|
||||
pass
|
||||
elif response == gtk.RESPONSE_ACCEPT:
|
||||
database = dlg.get_database()
|
||||
selection = self.treeDatabases.get_selection()
|
||||
if selection is not None:
|
||||
model, it = selection.get_selected()
|
||||
model.set_value(it, self.treeDatabaseColumns['name'], database.name)
|
||||
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):
|
||||
hasSelection = bool(treeSelection.count_selected_rows())
|
||||
|
||||
# enable/disable selection dependend widgets
|
||||
self.buttonDatabaseActivate.set_sensitive(hasSelection)
|
||||
self.buttonDatabaseEdit.set_sensitive(hasSelection)
|
||||
self.buttonDatabaseRemove.set_sensitive(hasSelection)
|
||||
#self.buttonDatabaseDelete.set_sensitive(hasSelection)
|
||||
|
||||
|
||||
class DialogDatabaseManager(gtk.Dialog):
|
||||
def __init__(self, databaseManager, parent=None):
|
||||
gtk.Dialog.__init__(self,
|
||||
title="Databases",
|
||||
parent=parent,
|
||||
flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
buttons=(
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
))
|
||||
#self.set_size_request(260, 250)
|
||||
self.widgetDatabaseManager = WidgetDatabaseManager(databaseManager, parentWidget=self)
|
||||
self.vbox.pack_start(self.widgetDatabaseManager, True, True)
|
||||
self.show_all()
|
||||
|
||||
#**************************************************************************************************
|
||||
if __name__ == '__main__':
|
||||
databaseManager = DatabaseManager.from_fpdb('', defaultDatabaseType=DatabaseTypeSqLite)
|
||||
|
||||
#d = DialogDatabaseProperties(
|
||||
# DatabaseManager(defaultDatabaseType=DatabaseTypeSqLite),
|
||||
#database=DatabaseTypePostgres(),
|
||||
# database=None,
|
||||
# )
|
||||
d = DialogDatabaseManager(databaseManager)
|
||||
d.connect("destroy", gtk.main_quit)
|
||||
d.run()
|
||||
#gtk.main()
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
@ -59,6 +59,8 @@ class DerivedStats():
|
|||
self.handsplayers[player[1]]['foldSbToStealChance'] = False
|
||||
self.handsplayers[player[1]]['foldedSbToSteal'] = False
|
||||
self.handsplayers[player[1]]['foldedBbToSteal'] = False
|
||||
self.handsplayers[player[1]]['tourneyTypeId'] = None
|
||||
|
||||
for i in range(5):
|
||||
self.handsplayers[player[1]]['street%dCalls' % i] = 0
|
||||
self.handsplayers[player[1]]['street%dBets' % i] = 0
|
||||
|
@ -70,9 +72,8 @@ class DerivedStats():
|
|||
self.handsplayers[player[1]]['street%dCheckCallRaiseDone' %i] = False
|
||||
self.handsplayers[player[1]]['otherRaisedStreet%d' %i] = False
|
||||
self.handsplayers[player[1]]['foldToOtherRaisedStreet%d' %i] = False
|
||||
|
||||
|
||||
#FIXME - Everything below this point is incomplete.
|
||||
self.handsplayers[player[1]]['tourneyTypeId'] = 1
|
||||
for i in range(1,5):
|
||||
self.handsplayers[player[1]]['foldToStreet%dCBChance' %i] = False
|
||||
self.handsplayers[player[1]]['foldToStreet%dCBDone' %i] = False
|
||||
|
@ -97,11 +98,12 @@ class DerivedStats():
|
|||
self.hands['tableName'] = hand.tablename
|
||||
self.hands['siteHandNo'] = hand.handid
|
||||
self.hands['gametypeId'] = None # Leave None, handled later after checking db
|
||||
self.hands['handStart'] = hand.starttime # format this!
|
||||
self.hands['startTime'] = hand.startTime # format this!
|
||||
self.hands['importTime'] = None
|
||||
self.hands['seats'] = self.countPlayers(hand)
|
||||
self.hands['maxSeats'] = hand.maxseats
|
||||
self.hands['texture'] = None # No calculation done for this yet.
|
||||
self.hands['tourneyId'] = hand.tourneyId
|
||||
|
||||
# This (i think...) is correct for both stud and flop games, as hand.board['street'] disappears, and
|
||||
# those values remain default in stud.
|
||||
|
@ -139,6 +141,12 @@ class DerivedStats():
|
|||
for player in hand.players:
|
||||
self.handsplayers[player[1]]['seatNo'] = player[0]
|
||||
self.handsplayers[player[1]]['startCash'] = int(100 * Decimal(player[2]))
|
||||
self.handsplayers[player[1]]['sitout'] = False #TODO: implement actual sitout detection
|
||||
if hand.gametype["type"]=="tour":
|
||||
self.handsplayers[player[1]]['tourneyTypeId']=hand.tourneyTypeId
|
||||
self.handsplayers[player[1]]['tourneysPlayersIds'] = hand.tourneysPlayersIds[player[1]]
|
||||
else:
|
||||
self.handsplayers[player[1]]['tourneysPlayersIds'] = None
|
||||
|
||||
# XXX: enumerate(list, start=x) is python 2.6 syntax; 'start'
|
||||
#for i, street in enumerate(hand.actionStreets[2:], start=1):
|
||||
|
@ -431,6 +439,11 @@ class DerivedStats():
|
|||
self.handsplayers[player[1]]['street%sAggr' % i] = True
|
||||
else:
|
||||
self.handsplayers[player[1]]['street%sAggr' % i] = False
|
||||
|
||||
if len(aggrers)>0 and i>0:
|
||||
for playername in others:
|
||||
self.handsplayers[playername]['otherRaisedStreet%s' % i] = True
|
||||
#print "otherRaised detected on handid "+str(hand.handid)+" for "+playername+" on street "+str(i)
|
||||
|
||||
if i > 0 and len(aggrers) > 0:
|
||||
for playername in others:
|
||||
|
@ -450,8 +463,7 @@ class DerivedStats():
|
|||
for act in hand.actions[hand.actionStreets[i+1]]:
|
||||
if act[1] in ('bets'):
|
||||
self.handsplayers[act[0]]['street%sBets' % i] = 1 + self.handsplayers[act[0]]['street%sBets' % i]
|
||||
|
||||
|
||||
|
||||
def folds(self, hand, i):
|
||||
for act in hand.actions[hand.actionStreets[i+1]]:
|
||||
if act[1] in ('folds'):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -154,7 +154,7 @@ or None if we fail to get the info """
|
|||
# 2008/11/10 3:58:52 ET
|
||||
#TODO: Do conversion from GMT to ET
|
||||
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
|
||||
hand.startTime = datetime.datetime.strptime(m.group('DATETIME'), "%Y/%m/%d - %H:%M:%S")
|
||||
return
|
||||
|
||||
def readPlayerStacks(self, hand):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Matt Turnbull
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -22,7 +22,7 @@ import gtk
|
|||
import os
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
from time import *
|
||||
from time import gmtime, mktime, strftime, strptime
|
||||
import gobject
|
||||
#import pokereval
|
||||
|
||||
|
@ -35,7 +35,7 @@ import Configuration
|
|||
import Database
|
||||
import SQL
|
||||
import Charset
|
||||
|
||||
import Filters
|
||||
|
||||
class Filters(threading.Thread):
|
||||
def __init__(self, db, config, qdict, display = {}, debug=True):
|
||||
|
@ -66,6 +66,7 @@ class Filters(threading.Thread):
|
|||
# Outer Packing box
|
||||
self.mainVBox = gtk.VBox(False, 0)
|
||||
|
||||
self.found = {'nl':False, 'fl':False, 'pl':False, 'ring':False, 'tour':False}
|
||||
self.label = {}
|
||||
self.callback = {}
|
||||
|
||||
|
@ -226,18 +227,22 @@ class Filters(threading.Thread):
|
|||
|
||||
def getNumHands(self):
|
||||
return self.numHands
|
||||
#end def getNumHands
|
||||
|
||||
def getSites(self):
|
||||
return self.sites
|
||||
#end def getSites
|
||||
|
||||
def getGames(self):
|
||||
return self.games
|
||||
|
||||
def getSiteIds(self):
|
||||
return self.siteid
|
||||
#end def getSiteIds
|
||||
|
||||
def getHeroes(self):
|
||||
return self.heroes
|
||||
#end def getHeroes
|
||||
|
||||
def getLimits(self):
|
||||
ltuple = []
|
||||
|
@ -255,12 +260,14 @@ class Filters(threading.Thread):
|
|||
if 'to' in self.sbSeats:
|
||||
self.seats['to'] = self.sbSeats['to'].get_value_as_int()
|
||||
return self.seats
|
||||
#end def getSeats
|
||||
|
||||
def getGroups(self):
|
||||
return self.groups
|
||||
|
||||
def getDates(self):
|
||||
return self.__get_dates()
|
||||
#end def getDates
|
||||
|
||||
def registerButton1Name(self, title):
|
||||
self.Button1.set_label(title)
|
||||
|
@ -274,11 +281,13 @@ class Filters(threading.Thread):
|
|||
def registerButton2Name(self, title):
|
||||
self.Button2.set_label(title)
|
||||
self.label['button2'] = title
|
||||
#end def registerButton2Name
|
||||
|
||||
def registerButton2Callback(self, callback):
|
||||
self.Button2.connect("clicked", callback, "clicked")
|
||||
self.Button2.set_sensitive(True)
|
||||
self.callback['button2'] = callback
|
||||
#end def registerButton2Callback
|
||||
|
||||
def cardCallback(self, widget, data=None):
|
||||
log.debug( "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()]) )
|
||||
|
@ -313,14 +322,16 @@ class Filters(threading.Thread):
|
|||
# get_text() returns a str but we want internal variables to be unicode:
|
||||
_guiname = unicode(_name)
|
||||
self.heroes[site] = _guiname
|
||||
# log.debug("setting heroes[%s]: %s"%(site, self.heroes[site]))
|
||||
#log.debug("setting heroes[%s]: %s"%(site, self.heroes[site]))
|
||||
#end def __set_hero_name
|
||||
|
||||
def __set_num_hands(self, w, val):
|
||||
try:
|
||||
self.numHands = int(w.get_text())
|
||||
except:
|
||||
self.numHands = 0
|
||||
# log.debug("setting numHands:", self.numHands)
|
||||
#log.debug("setting numHands:", self.numHands)
|
||||
#end def __set_num_hands
|
||||
|
||||
def createSiteLine(self, hbox, site):
|
||||
cb = gtk.CheckButton(site)
|
||||
|
@ -351,9 +362,10 @@ class Filters(threading.Thread):
|
|||
#print w.get_active()
|
||||
self.games[game] = w.get_active()
|
||||
log.debug("self.games[%s] set to %s" %(game, self.games[game]))
|
||||
#end def __set_game_select
|
||||
|
||||
def __set_limit_select(self, w, limit):
|
||||
#print w.get_active()
|
||||
#print "__set_limit_select: limit =", limit, w.get_active()
|
||||
self.limits[limit] = w.get_active()
|
||||
log.debug("self.limit[%s] set to %s" %(limit, self.limits[limit]))
|
||||
if limit.isdigit() or (len(limit) > 2 and (limit[-2:] == 'nl' or limit[-2:] == 'fl' or limit[-2:] == 'pl')):
|
||||
|
@ -597,6 +609,7 @@ class Filters(threading.Thread):
|
|||
else:
|
||||
print "INFO: No games returned from database"
|
||||
log.info("No games returned from database")
|
||||
#end def fillGamesFrame
|
||||
|
||||
def fillLimitsFrame(self, vbox, display):
|
||||
top_hbox = gtk.HBox(False, 0)
|
||||
|
@ -612,10 +625,10 @@ class Filters(threading.Thread):
|
|||
vbox.pack_start(vbox1, False, False, 0)
|
||||
self.boxes['limits'] = vbox1
|
||||
|
||||
self.cursor.execute(self.sql.query['getLimits3'])
|
||||
self.cursor.execute(self.sql.query['getCashLimits'])
|
||||
# selects limitType, bigBlind
|
||||
result = self.db.cursor.fetchall()
|
||||
found = {'nl':False, 'fl':False, 'pl':False, 'ring':False, 'tour':False}
|
||||
self.found = {'nl':False, 'fl':False, 'pl':False, 'ring':False, 'tour':False}
|
||||
|
||||
if len(result) >= 1:
|
||||
hbox = gtk.HBox(True, 0)
|
||||
|
@ -636,16 +649,16 @@ class Filters(threading.Thread):
|
|||
if True: #line[0] == 'ring':
|
||||
if line[1] == 'fl':
|
||||
name = str(line[2])
|
||||
found['fl'] = True
|
||||
self.found['fl'] = True
|
||||
elif line[1] == 'pl':
|
||||
name = str(line[2])+line[1]
|
||||
found['pl'] = True
|
||||
self.found['pl'] = True
|
||||
else:
|
||||
name = str(line[2])+line[1]
|
||||
found['nl'] = True
|
||||
self.found['nl'] = True
|
||||
self.cbLimits[name] = self.createLimitLine(hbox, name, name)
|
||||
self.types[name] = line[0]
|
||||
found[line[0]] = True # type is ring/tour
|
||||
self.found[line[0]] = True # type is ring/tour
|
||||
self.type = line[0] # if only one type, set it now
|
||||
if "LimitSep" in display and display["LimitSep"] == True and len(result) >= 2:
|
||||
hbox = gtk.HBox(True, 0)
|
||||
|
@ -663,24 +676,30 @@ class Filters(threading.Thread):
|
|||
self.cbNoLimits = self.createLimitLine(hbox, 'none', self.filterText['limitsnone'])
|
||||
|
||||
dest = vbox3 # for ring/tour buttons
|
||||
if "LimitType" in display and display["LimitType"] == True and found['nl'] and found['fl']:
|
||||
#if found['fl']:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox3.pack_start(hbox, False, False, 0)
|
||||
self.cbFL = self.createLimitLine(hbox, 'fl', self.filterText['limitsFL'])
|
||||
#if found['nl']:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox3.pack_start(hbox, False, False, 0)
|
||||
self.cbNL = self.createLimitLine(hbox, 'nl', self.filterText['limitsNL'])
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox3.pack_start(hbox, False, False, 0)
|
||||
self.cbPL = self.createLimitLine(hbox, 'pl', self.filterText['limitsPL'])
|
||||
dest = vbox2 # for ring/tour buttons
|
||||
if "LimitType" in display and display["LimitType"] == True:
|
||||
num_limit_types = 0
|
||||
if self.found['fl']: num_limit_types = num_limit_types + 1
|
||||
if self.found['pl']: num_limit_types = num_limit_types + 1
|
||||
if self.found['nl']: num_limit_types = num_limit_types + 1
|
||||
if num_limit_types > 1:
|
||||
if self.found['fl']:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox3.pack_start(hbox, False, False, 0)
|
||||
self.cbFL = self.createLimitLine(hbox, 'fl', self.filterText['limitsFL'])
|
||||
if self.found['nl']:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox3.pack_start(hbox, False, False, 0)
|
||||
self.cbNL = self.createLimitLine(hbox, 'nl', self.filterText['limitsNL'])
|
||||
if self.found['pl']:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox3.pack_start(hbox, False, False, 0)
|
||||
self.cbPL = self.createLimitLine(hbox, 'pl', self.filterText['limitsPL'])
|
||||
dest = vbox2 # for ring/tour buttons
|
||||
else:
|
||||
print "INFO: No games returned from database"
|
||||
log.info("No games returned from database")
|
||||
|
||||
if "Type" in display and display["Type"] == True and found['ring'] and found['tour']:
|
||||
if "Type" in display and display["Type"] == True and self.found['ring'] and self.found['tour']:
|
||||
rb1 = gtk.RadioButton(None, self.filterText['ring'])
|
||||
rb1.connect('clicked', self.__set_limit_select, 'ring')
|
||||
rb2 = gtk.RadioButton(rb1, self.filterText['tour'])
|
||||
|
@ -729,6 +748,7 @@ class Filters(threading.Thread):
|
|||
|
||||
self.sbSeats['from'] = sb1
|
||||
self.sbSeats['to'] = sb2
|
||||
#end def fillSeatsFrame
|
||||
|
||||
def fillGroupsFrame(self, vbox, display):
|
||||
hbox = gtk.HBox(False, 0)
|
||||
|
@ -833,6 +853,7 @@ class Filters(threading.Thread):
|
|||
hbox.pack_start(self.end_date, expand=False, padding=2)
|
||||
|
||||
hbox.pack_start(btn_clear, expand=False, padding=15)
|
||||
#end def fillDateFrame
|
||||
|
||||
def __refresh(self, widget, entry):
|
||||
for w in self.mainVBox.get_children():
|
||||
|
@ -846,6 +867,7 @@ class Filters(threading.Thread):
|
|||
else:
|
||||
self.boxes[entry].show()
|
||||
widget.set_label("hide")
|
||||
#end def __toggle_box
|
||||
|
||||
def __calendar_dialog(self, widget, entry):
|
||||
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
|
@ -863,10 +885,12 @@ class Filters(threading.Thread):
|
|||
d.add(vb)
|
||||
d.set_position(gtk.WIN_POS_MOUSE)
|
||||
d.show_all()
|
||||
#end def __calendar_dialog
|
||||
|
||||
def __clear_dates(self, w):
|
||||
self.start_date.set_text('')
|
||||
self.end_date.set_text('')
|
||||
#end def __clear_dates
|
||||
|
||||
def __get_dates(self):
|
||||
# self.day_start gives user's start of day in hours
|
||||
|
@ -891,6 +915,7 @@ class Filters(threading.Thread):
|
|||
log.info("t1="+t1+" adj_t1="+adj_t1+'.')
|
||||
|
||||
return (adj_t1, adj_t2)
|
||||
#end def __get_dates
|
||||
|
||||
def __get_date(self, widget, calendar, entry, win):
|
||||
# year and day are correct, month is 0..11
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
#!/usr/bin/python2
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
############################################################################
|
||||
#
|
||||
# File for DB queries used in fpdb
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
class FpdbSQLQueries:
|
||||
|
||||
def __init__(self, db):
|
||||
self.query = {}
|
||||
self.dbname = db
|
||||
|
||||
|
||||
if(self.dbname == 'MySQL InnoDB' or self.dbname == 'PostgreSQL'):
|
||||
self.query['set tx level'] = """SET SESSION TRANSACTION
|
||||
ISOLATION LEVEL READ COMMITTED"""
|
||||
elif(self.dbname == 'SQLite'):
|
||||
self.query['set tx level'] = """ """
|
||||
|
||||
|
||||
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL'):
|
||||
self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
|
||||
elif(self.dbname == 'SQLite'):
|
||||
self.query['getSiteId'] = """SELECT id from Sites where name = %s"""
|
||||
|
||||
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'):
|
||||
self.query['getGames'] = """SELECT DISTINCT category from Gametypes"""
|
||||
|
||||
if(self.dbname == 'MySQL InnoDB') or (self.dbname == 'PostgreSQL') or (self.dbname == 'SQLite'):
|
||||
self.query['getLimits'] = """SELECT DISTINCT bigBlind from Gametypes ORDER by bigBlind DESC"""
|
||||
|
||||
|
||||
if __name__== "__main__":
|
||||
from optparse import OptionParser
|
||||
|
||||
print "FpdbSQLQueries starting from CLI"
|
||||
|
||||
#process CLI parameters
|
||||
usage = "usage: %prog [options]"
|
||||
parser = OptionParser()
|
||||
parser.add_option("-t", "--type", dest="dbtype", help="Available 'MySQL InnoDB', 'PostgreSQL', 'SQLite'(default: MySQL InnoDB)", default="MySQL InnoDB")
|
||||
parser.add_option("-s", "--show", action="store_true", dest="showsql", help="Show full SQL output")
|
||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose")
|
||||
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.verbose:
|
||||
print """No additional output available in this file"""
|
||||
|
||||
obj = FpdbSQLQueries(options.dbtype)
|
||||
|
||||
print "Available Queries for '" + options.dbtype + "':"
|
||||
|
||||
for key in obj.query:
|
||||
print " " + key
|
||||
if options.showsql:
|
||||
print obj.query[key]
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -20,6 +20,7 @@
|
|||
|
||||
import logging
|
||||
from HandHistoryConverter import *
|
||||
#import TourneySummary
|
||||
|
||||
# Fulltilt HH Format converter
|
||||
|
||||
|
@ -53,7 +54,7 @@ class Fulltilt(HandHistoryConverter):
|
|||
\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\s(Ante\s\$?(?P<ANTE>[.0-9]+)\s)?-\s
|
||||
\$?(?P<CAP>[.0-9]+\sCap\s)?
|
||||
(?P<GAMETYPE>[a-zA-Z\/\'\s]+)\s-\s
|
||||
(?P<DATETIME>\d+:\d+:\d+\s\w+\s-\s\d+/\d+/\d+)\s?
|
||||
(?P<DATETIME>\d+:\d+:\d+\s\w+\s-\s\d+/\d+/\d+|\d+:\d+\s\w+\s-\s\w+\,\s\w+\s\d+\,\s\d+)
|
||||
(?P<PARTIAL>\(partial\))?\n
|
||||
(?:.*?\n(?P<CANCELLED>Hand\s\#(?P=HID)\shas\sbeen\scanceled))?
|
||||
''', re.VERBOSE|re.DOTALL)
|
||||
|
@ -66,7 +67,7 @@ class Fulltilt(HandHistoryConverter):
|
|||
''', re.VERBOSE)
|
||||
re_Button = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE)
|
||||
re_PlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE)
|
||||
re_TourneyPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
|
||||
re_TourneysPlayerInfo = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{2,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE)
|
||||
re_Board = re.compile(r"\[(?P<CARDS>.+)\]")
|
||||
|
||||
#static regex for tourney purpose
|
||||
|
@ -87,8 +88,8 @@ class Fulltilt(HandHistoryConverter):
|
|||
re_TourneyBuyInChips = re.compile("Buy-In Chips: (?P<BUYINCHIPS>\d+)")
|
||||
re_TourneyEntries = re.compile("(?P<ENTRIES>\d+) Entries")
|
||||
re_TourneyPrizePool = re.compile("Total Prize Pool: (?P<PRIZEPOOL_CURRENCY>\$|)?(?P<PRIZEPOOL>[.,0-9]+)")
|
||||
re_TourneyRebuyAmount = re.compile("Rebuy: (?P<REBUY_CURRENCY>\$|)?(?P<REBUY_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyAddOnAmount = re.compile("Add-On: (?P<ADDON_CURRENCY>\$|)?(?P<ADDON_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyRebuyCost = re.compile("Rebuy: (?P<REBUY_CURRENCY>\$|)?(?P<REBUY_COST>[.,0-9]+)")
|
||||
re_TourneyAddOnCost = re.compile("Add-On: (?P<ADDON_CURRENCY>\$|)?(?P<ADDON_COST>[.,0-9]+)")
|
||||
re_TourneyRebuyCount = re.compile("performed (?P<REBUY_COUNT>\d+) Rebuy")
|
||||
re_TourneyAddOnCount = re.compile("performed (?P<ADDON_COUNT>\d+) Add-On")
|
||||
re_TourneyRebuysTotal = re.compile("Total Rebuys: (?P<REBUY_TOTAL>\d+)")
|
||||
|
@ -96,10 +97,10 @@ class Fulltilt(HandHistoryConverter):
|
|||
re_TourneyRebuyChips = re.compile("Rebuy Chips: (?P<REBUY_CHIPS>\d+)")
|
||||
re_TourneyAddOnChips = re.compile("Add-On Chips: (?P<ADDON_CHIPS>\d+)")
|
||||
re_TourneyKOBounty = re.compile("Knockout Bounty: (?P<KO_BOUNTY_CURRENCY>\$|)?(?P<KO_BOUNTY_AMOUNT>[.,0-9]+)")
|
||||
re_TourneyCountKO = re.compile("received (?P<COUNT_KO>\d+) Knockout Bounty Award(s)?")
|
||||
re_TourneyKoCount = re.compile("received (?P<COUNT_KO>\d+) Knockout Bounty Award(s)?")
|
||||
re_TourneyTimeInfo = re.compile("Tournament started: (?P<STARTTIME>.*)\nTournament ((?P<IN_PROGRESS>is still in progress)?|(finished:(?P<ENDTIME>.*))?)$")
|
||||
|
||||
re_TourneyPlayersSummary = re.compile("^(?P<RANK>(Still Playing|\d+))( - |: )(?P<PNAME>[^\n,]+)(, )?(?P<WINNING_CURRENCY>\$|)?(?P<WINNING>[.\d]+)?", re.MULTILINE)
|
||||
re_TourneysPlayersSummary = re.compile("^(?P<RANK>(Still Playing|\d+))( - |: )(?P<PNAME>[^\n,]+)(, )?(?P<WINNING_CURRENCY>\$|)?(?P<WINNING>[.\d]+)?", re.MULTILINE)
|
||||
re_TourneyHeroFinishingP = re.compile("(?P<HERO_NAME>.*) finished in (?P<HERO_FINISHING_POS>\d+)(st|nd|rd|th) place")
|
||||
|
||||
#TODO: See if we need to deal with play money tourney summaries -- Not right now (they shouldn't pass the re_TourneyInfo)
|
||||
|
@ -200,8 +201,14 @@ class Fulltilt(HandHistoryConverter):
|
|||
return None
|
||||
hand.handid = m.group('HID')
|
||||
hand.tablename = m.group('TABLE')
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
|
||||
|
||||
|
||||
try:
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M:%S ET - %Y/%m/%d")
|
||||
except:
|
||||
hand.starttime = datetime.datetime.strptime(m.group('DATETIME'), "%H:%M ET - %a, %B %d, %Y")
|
||||
|
||||
hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC")
|
||||
|
||||
if m.group("CANCELLED") or m.group("PARTIAL"):
|
||||
raise FpdbParseError(hid=m.group('HID'))
|
||||
|
||||
|
@ -221,7 +228,14 @@ class Fulltilt(HandHistoryConverter):
|
|||
else:
|
||||
hand.tourneyComment = n.group('TOURNEY_NAME') # can be None
|
||||
if (n.group('CURRENCY') is not None and n.group('BUYIN') is not None and n.group('FEE') is not None):
|
||||
hand.buyin = "%s%s+%s%s" %(n.group('CURRENCY'), n.group('BUYIN'), n.group('CURRENCY'), n.group('FEE'))
|
||||
if n.group('CURRENCY')=="$":
|
||||
hand.buyinCurrency="USD"
|
||||
elif n.group('CURRENCY')==u"€":
|
||||
hand.buyinCurrency="EUR"
|
||||
else:
|
||||
hand.buyinCurrency="NA"
|
||||
hand.buyin = int(100*Decimal(n.group('BUYIN')))
|
||||
hand.fee = int(100*Decimal(n.group('FEE')))
|
||||
if n.group('TURBO') is not None :
|
||||
hand.speed = "Turbo"
|
||||
if n.group('SPECIAL') is not None :
|
||||
|
@ -231,7 +245,7 @@ class Fulltilt(HandHistoryConverter):
|
|||
if special == "KO":
|
||||
hand.isKO = True
|
||||
if special == "Head's Up":
|
||||
hand.isHU = True
|
||||
hand.maxSeats = 2
|
||||
if re.search("Matrix", special):
|
||||
hand.isMatrix = True
|
||||
if special == "Shootout":
|
||||
|
@ -264,7 +278,7 @@ class Fulltilt(HandHistoryConverter):
|
|||
if hand.gametype['type'] == "ring" :
|
||||
m = self.re_PlayerInfo.finditer(pre)
|
||||
else: #if hand.gametype['type'] == "tour"
|
||||
m = self.re_TourneyPlayerInfo.finditer(pre)
|
||||
m = self.re_TourneysPlayerInfo.finditer(pre)
|
||||
|
||||
for a in m:
|
||||
hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
|
||||
|
@ -426,23 +440,23 @@ class Fulltilt(HandHistoryConverter):
|
|||
def readSummaryInfo(self, summaryInfoList):
|
||||
self.status = True
|
||||
|
||||
m = re.search("Tournament Summary", summaryInfoList[0])
|
||||
if m:
|
||||
# info list should be 2 lines : Tourney infos & Finsihing postions with winnings
|
||||
if (len(summaryInfoList) != 2 ):
|
||||
log.info("Too many lines (%d) in file '%s' : '%s'" % (len(summaryInfoList), self.in_path, summaryInfoList) )
|
||||
self.status = False
|
||||
else:
|
||||
self.tourney = Tourney.Tourney(sitename = self.sitename, gametype = None, summaryText = summaryInfoList, builtFrom = "HHC")
|
||||
self.status = self.getPlayersPositionsAndWinnings(self.tourney)
|
||||
if self.status == True :
|
||||
self.status = self.determineTourneyType(self.tourney)
|
||||
#print self.tourney
|
||||
else:
|
||||
log.info("Parsing NOK : rejected")
|
||||
else:
|
||||
log.info( "This is not a summary file : '%s'" % (self.in_path) )
|
||||
self.status = False
|
||||
#m = re.search("Tournament Summary", summaryInfoList[0])
|
||||
#if m:
|
||||
# # info list should be 2 lines : Tourney infos & Finsihing postions with winnings
|
||||
# if (len(summaryInfoList) != 2 ):
|
||||
# log.info("Too many or too few lines (%d) in file '%s' : '%s'" % (len(summaryInfoList), self.in_path, summaryInfoList) )
|
||||
# self.status = False
|
||||
# else:
|
||||
# self.tourney = TourneySummary.TourneySummary(sitename = self.sitename, gametype = None, summaryText = summaryInfoList, builtFrom = "HHC")
|
||||
# self.status = self.getPlayersPositionsAndWinnings(self.tourney)
|
||||
# if self.status == True :
|
||||
# self.status = self.determineTourneyType(self.tourney)
|
||||
# #print self.tourney
|
||||
# else:
|
||||
# log.info("Parsing NOK : rejected")
|
||||
#else:
|
||||
# log.info( "This is not a summary file : '%s'" % (self.in_path) )
|
||||
# self.status = False
|
||||
|
||||
return self.status
|
||||
|
||||
|
@ -497,7 +511,6 @@ class Fulltilt(HandHistoryConverter):
|
|||
if special == "KO":
|
||||
tourney.isKO = True
|
||||
if special == "Heads Up":
|
||||
tourney.isHU = True
|
||||
tourney.maxseats = 2
|
||||
if re.search("Matrix", special):
|
||||
tourney.isMatrix = True
|
||||
|
@ -566,8 +579,8 @@ class Fulltilt(HandHistoryConverter):
|
|||
dictRegex = { "BUYINCHIPS" : self.re_TourneyBuyInChips,
|
||||
"ENTRIES" : self.re_TourneyEntries,
|
||||
"PRIZEPOOL" : self.re_TourneyPrizePool,
|
||||
"REBUY_AMOUNT" : self.re_TourneyRebuyAmount,
|
||||
"ADDON_AMOUNT" : self.re_TourneyAddOnAmount,
|
||||
"REBUY_COST" : self.re_TourneyRebuyCost,
|
||||
"ADDON_COST" : self.re_TourneyAddOnCost,
|
||||
"REBUY_TOTAL" : self.re_TourneyRebuysTotal,
|
||||
"ADDONS_TOTAL" : self.re_TourneyAddOnsTotal,
|
||||
"REBUY_CHIPS" : self.re_TourneyRebuyChips,
|
||||
|
@ -580,10 +593,10 @@ class Fulltilt(HandHistoryConverter):
|
|||
dictHolders = { "BUYINCHIPS" : "buyInChips",
|
||||
"ENTRIES" : "entries",
|
||||
"PRIZEPOOL" : "prizepool",
|
||||
"REBUY_AMOUNT" : "rebuyAmount",
|
||||
"ADDON_AMOUNT" : "addOnAmount",
|
||||
"REBUY_TOTAL" : "totalRebuys",
|
||||
"ADDONS_TOTAL" : "totalAddOns",
|
||||
"REBUY_COST" : "rebuyCost",
|
||||
"ADDON_COST" : "addOnCost",
|
||||
"REBUY_TOTAL" : "totalRebuyCount",
|
||||
"ADDONS_TOTAL" : "totalAddOnCount",
|
||||
"REBUY_CHIPS" : "rebuyChips",
|
||||
"ADDON_CHIPS" : "addOnChips",
|
||||
"STARTTIME" : "starttime",
|
||||
|
@ -607,42 +620,39 @@ class Fulltilt(HandHistoryConverter):
|
|||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if mg['REBUY_COUNT'] is not None :
|
||||
tourney.countRebuys.update( { tourney.hero : Decimal(mg['REBUY_COUNT']) } )
|
||||
tourney.rebuyCounts.update( { tourney.hero : Decimal(mg['REBUY_COUNT']) } )
|
||||
m = self.re_TourneyAddOnCount.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if mg['ADDON_COUNT'] is not None :
|
||||
tourney.countAddOns.update( { tourney.hero : Decimal(mg['ADDON_COUNT']) } )
|
||||
m = self.re_TourneyCountKO.search(tourneyText)
|
||||
tourney.addOnCounts.update( { tourney.hero : Decimal(mg['ADDON_COUNT']) } )
|
||||
m = self.re_TourneyKoCount.search(tourneyText)
|
||||
if m is not None:
|
||||
mg = m.groupdict()
|
||||
if mg['COUNT_KO'] is not None :
|
||||
tourney.countKO.update( { tourney.hero : Decimal(mg['COUNT_KO']) } )
|
||||
tourney.koCounts.update( { tourney.hero : Decimal(mg['COUNT_KO']) } )
|
||||
|
||||
# Deal with money amounts
|
||||
tourney.koBounty = 100*Decimal(re.sub(u',', u'', "%s" % tourney.koBounty))
|
||||
tourney.prizepool = 100*Decimal(re.sub(u',', u'', "%s" % tourney.prizepool))
|
||||
tourney.rebuyAmount = 100*Decimal(re.sub(u',', u'', "%s" % tourney.rebuyAmount))
|
||||
tourney.addOnAmount = 100*Decimal(re.sub(u',', u'', "%s" % tourney.addOnAmount))
|
||||
tourney.rebuyCost = 100*Decimal(re.sub(u',', u'', "%s" % tourney.rebuyCost))
|
||||
tourney.addOnCost = 100*Decimal(re.sub(u',', u'', "%s" % tourney.addOnCost))
|
||||
|
||||
# Calculate payin amounts and update winnings -- not possible to take into account nb of rebuys, addons or Knockouts for other players than hero on FTP
|
||||
for p in tourney.players :
|
||||
tourney.payinAmounts[p] = tourney.buyin + tourney.fee + (tourney.rebuyAmount * tourney.countRebuys[p]) + (tourney.addOnAmount * tourney.countAddOns[p])
|
||||
#print " player %s : payinAmount = %d" %( p, tourney.payinAmounts[p])
|
||||
if tourney.isKO :
|
||||
#tourney.incrementPlayerWinnings(tourney.players[p], Decimal(tourney.koBounty)*Decimal(tourney.countKO[p]))
|
||||
tourney.winnings[p] += Decimal(tourney.koBounty)*Decimal(tourney.countKO[p])
|
||||
#tourney.incrementPlayerWinnings(tourney.players[p], Decimal(tourney.koBounty)*Decimal(tourney.koCounts[p]))
|
||||
tourney.winnings[p] += Decimal(tourney.koBounty)*Decimal(tourney.koCounts[p])
|
||||
#print "player %s : winnings %d" % (p, tourney.winnings[p])
|
||||
|
||||
|
||||
|
||||
#print mg
|
||||
return True
|
||||
#end def determineTourneyType
|
||||
|
||||
def getPlayersPositionsAndWinnings(self, tourney):
|
||||
playersText = tourney.summaryText[1]
|
||||
#print "Examine : '%s'" %(playersText)
|
||||
m = self.re_TourneyPlayersSummary.finditer(playersText)
|
||||
m = self.re_TourneysPlayersSummary.finditer(playersText)
|
||||
|
||||
for a in m:
|
||||
if a.group('PNAME') is not None and a.group('RANK') is not None:
|
||||
|
@ -656,7 +666,7 @@ class Fulltilt(HandHistoryConverter):
|
|||
else:
|
||||
winnings = "0"
|
||||
|
||||
tourney.addPlayer(rank, a.group('PNAME'), winnings, 0, 0, 0, 0)
|
||||
tourney.addPlayer(rank, a.group('PNAME'), winnings, "USD", 0, 0, 0) #TODO: make it store actual winnings currency
|
||||
else:
|
||||
print "FullTilt: Player finishing stats unreadable : %s" % a
|
||||
|
||||
|
@ -666,10 +676,10 @@ class Fulltilt(HandHistoryConverter):
|
|||
heroName = n.group('HERO_NAME')
|
||||
tourney.hero = heroName
|
||||
# Is this really useful ?
|
||||
if heroName not in tourney.finishPositions:
|
||||
print "FullTilt:", heroName, "not found in tourney.finishPositions ..."
|
||||
elif (tourney.finishPositions[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
|
||||
print "FullTilt: Bad parsing : finish position incoherent : %s / %s" % (tourney.finishPositions[heroName], n.group('HERO_FINISHING_POS'))
|
||||
if heroName not in tourney.ranks:
|
||||
print "FullTilt:", heroName, "not found in tourney.ranks ..."
|
||||
elif (tourney.ranks[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
|
||||
print "FullTilt: Bad parsing : finish position incoherent : %s / %s" % (tourney.ranks[heroName], n.group('HERO_FINISHING_POS'))
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -101,7 +101,7 @@ class GuiAutoImport (threading.Thread):
|
|||
hbox.pack_start(lbl1, expand=True, fill=False)
|
||||
|
||||
self.doAutoImportBool = False
|
||||
self.startButton = gtk.ToggleButton(" _Start Autoimport ")
|
||||
self.startButton = gtk.ToggleButton(" Start _Autoimport ")
|
||||
self.startButton.connect("clicked", self.startClicked, "start clicked")
|
||||
hbox.pack_start(self.startButton, expand=False, fill=False)
|
||||
|
||||
|
@ -156,7 +156,7 @@ class GuiAutoImport (threading.Thread):
|
|||
def do_import(self):
|
||||
"""Callback for timer to do an import iteration."""
|
||||
if self.doAutoImportBool:
|
||||
self.startButton.set_label(u' I M P O R T I N G ')
|
||||
self.startButton.set_label(u' _Auto Import Running ')
|
||||
self.importer.runUpdated()
|
||||
self.addText(".")
|
||||
#sys.stdout.write(".")
|
||||
|
@ -167,9 +167,9 @@ class GuiAutoImport (threading.Thread):
|
|||
|
||||
def reset_startbutton(self):
|
||||
if self.pipe_to_hud is not None:
|
||||
self.startButton.set_label(u' _Stop Autoimport ')
|
||||
self.startButton.set_label(u' Stop _Autoimport ')
|
||||
else:
|
||||
self.startButton.set_label(u' _Start Autoimport ')
|
||||
self.startButton.set_label(u' Start _Autoimport ')
|
||||
|
||||
return False
|
||||
|
||||
|
@ -246,7 +246,7 @@ class GuiAutoImport (threading.Thread):
|
|||
#print >>self.pipe_to_hud.stdin, "\n"
|
||||
self.pipe_to_hud.communicate('\n') # waits for process to terminate
|
||||
self.pipe_to_hud = None
|
||||
self.startButton.set_label(u' _Start Autoimport ')
|
||||
self.startButton.set_label(u' Start _Autoimport ')
|
||||
|
||||
#end def GuiAutoImport.startClicked
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -50,7 +50,7 @@ class GuiBulkImport():
|
|||
ttime = None
|
||||
# Does the lock acquisition need to be more sophisticated for multiple dirs?
|
||||
# (see comment above about what to do if pipe already open)
|
||||
if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired
|
||||
if self.settings['global_lock'].acquire(wait=False, source="GuiBulkImport"): # returns false immediately if lock not acquired
|
||||
#try:
|
||||
print "\nGlobal lock taken ..."
|
||||
self.progressbar.set_text("Importing...")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
@ -24,6 +24,8 @@ import gtk
|
|||
import gobject
|
||||
import pango
|
||||
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
# logging has been set up in fpdb.py or HUD_main.py, use their settings:
|
||||
log = logging.getLogger("logview")
|
||||
|
@ -135,7 +137,7 @@ class GuiLogView:
|
|||
def loadLog(self):
|
||||
|
||||
self.liststore.clear()
|
||||
self.listcols = []
|
||||
# self.listcols = [] blanking listcols causes sortcols() to fail with index out of range
|
||||
|
||||
# guesstimate number of lines in file
|
||||
if os.path.exists(self.logfile):
|
||||
|
@ -155,6 +157,15 @@ class GuiLogView:
|
|||
# 2009-12-02 15:23:21,716 - config DEBUG config logger initialised
|
||||
l = l + 1
|
||||
if l > startline:
|
||||
# NOTE selecting a sort column and then switching to a log file
|
||||
# with several thousand rows will send cpu 100% for a prolonged period.
|
||||
# reason is that the append() method seems to sort every record as it goes, rather than
|
||||
# pulling in the whole file and sorting at the end.
|
||||
# one fix is to check if a column sort has been selected, reset to date/time asc
|
||||
# append all the rows and then reselect the required sort order.
|
||||
# Note: there is no easy method available to revert the list to an "unsorted" state.
|
||||
# always defaulting to date/time asc doesn't work, because some rows do not have date/time info
|
||||
# and would end up sorted out of context.
|
||||
if len(line) > 49 and line[23:26] == ' - ' and line[34:39] == ' ':
|
||||
iter = self.liststore.append( (line[0:23], line[26:32], line[39:46], line[48:].strip(), True) )
|
||||
else:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
#Copyright 2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
@ -15,662 +15,8 @@
|
|||
#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.
|
||||
|
||||
import traceback
|
||||
import threading
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import os
|
||||
import sys
|
||||
from time import time, strftime
|
||||
|
||||
import Card
|
||||
import fpdb_import
|
||||
import Database
|
||||
import Filters
|
||||
import Charset
|
||||
|
||||
colalias,colshow,colheading,colxalign,colformat,coltype = 0,1,2,3,4,5
|
||||
ranks = {'x':0, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, 'T':10, 'J':11, 'Q':12, 'K':13, 'A':14}
|
||||
|
||||
class GuiPlayerStats (threading.Thread):
|
||||
|
||||
def __init__(self, config, querylist, mainwin, debug=True):
|
||||
self.debug = debug
|
||||
self.conf = config
|
||||
self.main_window = mainwin
|
||||
self.sql = querylist
|
||||
|
||||
self.liststore = [] # gtk.ListStore[] stores the contents of the grids
|
||||
self.listcols = [] # gtk.TreeViewColumn[][] stores the columns in the grids
|
||||
|
||||
self.MYSQL_INNODB = 2
|
||||
self.PGSQL = 3
|
||||
self.SQLITE = 4
|
||||
|
||||
# create new db connection to avoid conflicts with other threads
|
||||
self.db = Database.Database(self.conf, sql=self.sql)
|
||||
self.cursor = self.db.cursor
|
||||
|
||||
settings = {}
|
||||
settings.update(self.conf.get_db_parameters())
|
||||
settings.update(self.conf.get_tv_parameters())
|
||||
settings.update(self.conf.get_import_parameters())
|
||||
settings.update(self.conf.get_default_paths())
|
||||
|
||||
# text used on screen stored here so that it can be configured
|
||||
self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
|
||||
}
|
||||
|
||||
filters_display = { "Heroes" : True,
|
||||
"Sites" : True,
|
||||
"Games" : True,
|
||||
"Limits" : True,
|
||||
"LimitSep" : True,
|
||||
"LimitType" : True,
|
||||
"Type" : True,
|
||||
"Seats" : True,
|
||||
"SeatSep" : True,
|
||||
"Dates" : True,
|
||||
"Groups" : True,
|
||||
"GroupsAll" : True,
|
||||
"Button1" : True,
|
||||
"Button2" : True
|
||||
}
|
||||
|
||||
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
|
||||
self.filters.registerButton1Name("_Filters")
|
||||
self.filters.registerButton1Callback(self.showDetailFilter)
|
||||
self.filters.registerButton2Name("_Refresh Stats")
|
||||
self.filters.registerButton2Callback(self.refreshStats)
|
||||
|
||||
# ToDo: store in config
|
||||
# ToDo: create popup to adjust column config
|
||||
# columns to display, keys match column name returned by sql, values in tuple are:
|
||||
# is column displayed, column heading, xalignment, formatting, celltype
|
||||
self.columns = [ ["game", True, "Game", 0.0, "%s", "str"]
|
||||
, ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line
|
||||
, ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code)
|
||||
, ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code)
|
||||
, ["n", True, "Hds", 1.0, "%1.0f", "str"]
|
||||
, ["avgseats", False, "Seats", 1.0, "%3.1f", "str"]
|
||||
, ["vpip", True, "VPIP", 1.0, "%3.1f", "str"]
|
||||
, ["pfr", True, "PFR", 1.0, "%3.1f", "str"]
|
||||
, ["pf3", True, "PF3", 1.0, "%3.1f", "str"]
|
||||
, ["aggfac", True, "AggFac", 1.0, "%2.2f", "str"]
|
||||
, ["aggfrq", True, "AggFreq", 1.0, "%3.1f", "str"]
|
||||
, ["conbet", True, "ContBet", 1.0, "%3.1f", "str"]
|
||||
, ["steals", True, "Steals", 1.0, "%3.1f", "str"]
|
||||
, ["saw_f", True, "Saw_F", 1.0, "%3.1f", "str"]
|
||||
, ["sawsd", True, "SawSD", 1.0, "%3.1f", "str"]
|
||||
, ["wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f", "str"]
|
||||
, ["wmsd", True, "W$SD", 1.0, "%3.1f", "str"]
|
||||
, ["flafq", True, "FlAFq", 1.0, "%3.1f", "str"]
|
||||
, ["tuafq", True, "TuAFq", 1.0, "%3.1f", "str"]
|
||||
, ["rvafq", True, "RvAFq", 1.0, "%3.1f", "str"]
|
||||
, ["pofafq", False, "PoFAFq", 1.0, "%3.1f", "str"]
|
||||
, ["net", True, "Net($)", 1.0, "%6.2f", "cash"]
|
||||
, ["bbper100", True, "bb/100", 1.0, "%4.2f", "str"]
|
||||
, ["rake", True, "Rake($)", 1.0, "%6.2f", "cash"]
|
||||
, ["bb100xr", True, "bbxr/100", 1.0, "%4.2f", "str"]
|
||||
, ["variance", True, "Variance", 1.0, "%5.2f", "str"]
|
||||
]
|
||||
|
||||
# Detail filters: This holds the data used in the popup window, extra values are
|
||||
# added at the end of these lists during processing
|
||||
# sql test, screen description, min, max
|
||||
self.handtests = [ # already in filter class : ['h.seats', 'Number of Players', 2, 10]
|
||||
['h.maxSeats', 'Size of Table', 2, 10]
|
||||
,['h.playersVpi', 'Players who VPI', 0, 10]
|
||||
,['h.playersAtStreet1', 'Players at Flop', 0, 10]
|
||||
,['h.playersAtStreet2', 'Players at Turn', 0, 10]
|
||||
,['h.playersAtStreet3', 'Players at River', 0, 10]
|
||||
,['h.playersAtStreet4', 'Players at Street7', 0, 10]
|
||||
,['h.playersAtShowdown', 'Players at Showdown', 0, 10]
|
||||
,['h.street0Raises', 'Bets to See Flop', 0, 5]
|
||||
,['h.street1Raises', 'Bets to See Turn', 0, 5]
|
||||
,['h.street2Raises', 'Bets to See River', 0, 5]
|
||||
,['h.street3Raises', 'Bets to See Street7', 0, 5]
|
||||
,['h.street4Raises', 'Bets to See Showdown', 0, 5]
|
||||
]
|
||||
|
||||
self.stats_frame = None
|
||||
self.stats_vbox = None
|
||||
self.detailFilters = [] # the data used to enhance the sql select
|
||||
|
||||
#self.main_hbox = gtk.HBox(False, 0)
|
||||
#self.main_hbox.show()
|
||||
self.main_hbox = gtk.HPaned()
|
||||
|
||||
self.stats_frame = gtk.Frame()
|
||||
self.stats_frame.show()
|
||||
|
||||
self.stats_vbox = gtk.VPaned()
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
# self.fillStatsFrame(self.stats_vbox)
|
||||
|
||||
#self.main_hbox.pack_start(self.filters.get_vbox())
|
||||
#self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
|
||||
self.main_hbox.pack1(self.filters.get_vbox())
|
||||
self.main_hbox.pack2(self.stats_frame)
|
||||
self.main_hbox.show()
|
||||
|
||||
# make sure Hand column is not displayed
|
||||
[x for x in self.columns if x[0] == 'hand'][0][1] = False
|
||||
self.last_pos = -1
|
||||
|
||||
|
||||
def get_vbox(self):
|
||||
"""returns the vbox of this thread"""
|
||||
return self.main_hbox
|
||||
|
||||
def refreshStats(self, widget, data):
|
||||
self.last_pos = self.stats_vbox.get_position()
|
||||
try: self.stats_vbox.destroy()
|
||||
except AttributeError: pass
|
||||
self.liststore = []
|
||||
self.listcols = []
|
||||
#self.stats_vbox = gtk.VBox(False, 0)
|
||||
self.stats_vbox = gtk.VPaned()
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
self.fillStatsFrame(self.stats_vbox)
|
||||
if self.last_pos > 0:
|
||||
self.stats_vbox.set_position(self.last_pos)
|
||||
|
||||
def fillStatsFrame(self, vbox):
|
||||
sites = self.filters.getSites()
|
||||
heroes = self.filters.getHeroes()
|
||||
siteids = self.filters.getSiteIds()
|
||||
limits = self.filters.getLimits()
|
||||
type = self.filters.getType()
|
||||
seats = self.filters.getSeats()
|
||||
groups = self.filters.getGroups()
|
||||
dates = self.filters.getDates()
|
||||
games = self.filters.getGames()
|
||||
sitenos = []
|
||||
playerids = []
|
||||
|
||||
# Which sites are selected?
|
||||
for site in sites:
|
||||
if sites[site] == True:
|
||||
sitenos.append(siteids[site])
|
||||
_hname = Charset.to_utf8(heroes[site])
|
||||
result = self.db.get_player_id(self.conf, site, _hname)
|
||||
if result is not None:
|
||||
playerids.append(int(result))
|
||||
|
||||
if not sitenos:
|
||||
#Should probably pop up here.
|
||||
print "No sites selected - defaulting to PokerStars"
|
||||
sitenos = [2]
|
||||
if not playerids:
|
||||
print "No player ids found"
|
||||
return
|
||||
if not limits:
|
||||
print "No limits found"
|
||||
return
|
||||
|
||||
self.createStatsTable(vbox, playerids, sitenos, limits, type, seats, groups, dates, games)
|
||||
|
||||
def createStatsTable(self, vbox, playerids, sitenos, limits, type, seats, groups, dates, games):
|
||||
starttime = time()
|
||||
show_detail = True
|
||||
|
||||
# Scrolled window for summary table
|
||||
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
||||
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
swin.show()
|
||||
vbox.pack1(swin)
|
||||
|
||||
# Display summary table at top of page
|
||||
# 3rd parameter passes extra flags, currently includes:
|
||||
# holecards - whether to display card breakdown (True/False)
|
||||
# numhands - min number hands required when displaying all players
|
||||
# gridnum - index for grid data structures
|
||||
flags = [False, self.filters.getNumHands(), 0]
|
||||
self.addGrid(swin, 'playerDetailedStats', flags, playerids
|
||||
,sitenos, limits, type, seats, groups, dates, games)
|
||||
|
||||
if 'allplayers' in groups and groups['allplayers']:
|
||||
# can't currently do this combination so skip detailed table
|
||||
show_detail = False
|
||||
|
||||
if show_detail:
|
||||
# Separator
|
||||
vbox2 = gtk.VBox(False, 0)
|
||||
heading = gtk.Label(self.filterText['handhead'])
|
||||
heading.show()
|
||||
vbox2.pack_start(heading, expand=False, padding=3)
|
||||
|
||||
# Scrolled window for detailed table (display by hand)
|
||||
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
||||
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
swin.show()
|
||||
vbox2.pack_start(swin, expand=True, padding=3)
|
||||
vbox.pack2(vbox2)
|
||||
vbox2.show()
|
||||
|
||||
# Detailed table
|
||||
flags[0] = True
|
||||
flags[2] = 1
|
||||
self.addGrid(swin, 'playerDetailedStats', flags, playerids
|
||||
,sitenos, limits, type, seats, groups, dates, games)
|
||||
|
||||
self.db.rollback()
|
||||
print "Stats page displayed in %4.2f seconds" % (time() - starttime)
|
||||
#end def fillStatsFrame(self, vbox):
|
||||
|
||||
def reset_style_render_func(self, treeviewcolumn, cell, model, iter):
|
||||
cell.set_property('foreground', 'black')
|
||||
|
||||
def ledger_style_render_func(self, tvcol, cell, model, iter):
|
||||
str = cell.get_property('text')
|
||||
if '-' in str:
|
||||
str = str.replace("-", "")
|
||||
str = "(%s)" %(str)
|
||||
cell.set_property('text', str)
|
||||
cell.set_property('foreground', 'red')
|
||||
else:
|
||||
cell.set_property('foreground', 'darkgreen')
|
||||
|
||||
return
|
||||
|
||||
def sortnums(self, model, iter1, iter2, nums):
|
||||
try:
|
||||
ret = 0
|
||||
(n, grid) = nums
|
||||
a = self.liststore[grid].get_value(iter1, n)
|
||||
b = self.liststore[grid].get_value(iter2, n)
|
||||
if 'f' in self.cols_to_show[n][4]:
|
||||
try: a = float(a)
|
||||
except: a = 0.0
|
||||
try: b = float(b)
|
||||
except: b = 0.0
|
||||
if n == 0 and grid == 1: #make sure it only works on the starting hands
|
||||
a1,a2,a3 = ranks[a[0]], ranks[a[1]], (a+'o')[2]
|
||||
b1,b2,b3 = ranks[b[0]], ranks[b[1]], (b+'o')[2]
|
||||
if a1 > b1 or ( a1 == b1 and (a2 > b2 or (a2 == b2 and a3 > b3) ) ):
|
||||
ret = 1
|
||||
else:
|
||||
ret = -1
|
||||
else:
|
||||
if a < b:
|
||||
ret = -1
|
||||
elif a == b:
|
||||
ret = 0
|
||||
else:
|
||||
ret = 1
|
||||
#print "n =", n, "iter1[n] =", self.liststore[grid].get_value(iter1,n), "iter2[n] =", self.liststore[grid].get_value(iter2,n), "ret =", ret
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])
|
||||
print "***sortnums error: " + str(sys.exc_info()[1])
|
||||
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
|
||||
|
||||
return(ret)
|
||||
|
||||
def sortcols(self, col, nums):
|
||||
try:
|
||||
#This doesn't actually work yet - clicking heading in top section sorts bottom section :-(
|
||||
(n, grid) = nums
|
||||
if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
|
||||
col.set_sort_order(gtk.SORT_DESCENDING)
|
||||
else:
|
||||
col.set_sort_order(gtk.SORT_ASCENDING)
|
||||
self.liststore[grid].set_sort_column_id(n, col.get_sort_order())
|
||||
self.liststore[grid].set_sort_func(n, self.sortnums, (n,grid))
|
||||
for i in xrange(len(self.listcols[grid])):
|
||||
self.listcols[grid][i].set_sort_indicator(False)
|
||||
self.listcols[grid][n].set_sort_indicator(True)
|
||||
# use this listcols[col].set_sort_indicator(True)
|
||||
# to turn indicator off for other cols
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])
|
||||
print "***sortcols error: " + str(sys.exc_info()[1])
|
||||
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
|
||||
|
||||
def addGrid(self, vbox, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games):
|
||||
counter = 0
|
||||
row = 0
|
||||
sqlrow = 0
|
||||
if not flags: holecards,grid = False,0
|
||||
else: holecards,grid = flags[0],flags[2]
|
||||
|
||||
tmp = self.sql.query[query]
|
||||
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, type, seats, groups, dates, games)
|
||||
self.cursor.execute(tmp)
|
||||
result = self.cursor.fetchall()
|
||||
colnames = [desc[0].lower() for desc in self.cursor.description]
|
||||
|
||||
# pre-fetch some constant values:
|
||||
self.cols_to_show = [x for x in self.columns if x[colshow]]
|
||||
hgametypeid_idx = colnames.index('hgametypeid')
|
||||
|
||||
assert len(self.liststore) == grid, "len(self.liststore)="+str(len(self.liststore))+" grid-1="+str(grid)
|
||||
self.liststore.append( gtk.ListStore(*([str] * len(self.cols_to_show))) )
|
||||
view = gtk.TreeView(model=self.liststore[grid])
|
||||
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
|
||||
#vbox.pack_start(view, expand=False, padding=3)
|
||||
vbox.add(view)
|
||||
textcell = gtk.CellRendererText()
|
||||
textcell50 = gtk.CellRendererText()
|
||||
textcell50.set_property('xalign', 0.5)
|
||||
numcell = gtk.CellRendererText()
|
||||
numcell.set_property('xalign', 1.0)
|
||||
assert len(self.listcols) == grid
|
||||
self.listcols.append( [] )
|
||||
|
||||
# Create header row eg column: ("game", True, "Game", 0.0, "%s")
|
||||
for col, column in enumerate(self.cols_to_show):
|
||||
if column[colalias] == 'game' and holecards:
|
||||
s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
|
||||
else:
|
||||
s = column[colheading]
|
||||
self.listcols[grid].append(gtk.TreeViewColumn(s))
|
||||
view.append_column(self.listcols[grid][col])
|
||||
if column[colformat] == '%s':
|
||||
if column[colxalign] == 0.0:
|
||||
self.listcols[grid][col].pack_start(textcell, expand=True)
|
||||
self.listcols[grid][col].add_attribute(textcell, 'text', col)
|
||||
cellrend = textcell
|
||||
else:
|
||||
self.listcols[grid][col].pack_start(textcell50, expand=True)
|
||||
self.listcols[grid][col].add_attribute(textcell50, 'text', col)
|
||||
cellrend = textcell50
|
||||
self.listcols[grid][col].set_expand(True)
|
||||
else:
|
||||
self.listcols[grid][col].pack_start(numcell, expand=True)
|
||||
self.listcols[grid][col].add_attribute(numcell, 'text', col)
|
||||
self.listcols[grid][col].set_expand(True)
|
||||
cellrend = numcell
|
||||
#self.listcols[grid][col].set_alignment(column[colxalign]) # no effect?
|
||||
self.listcols[grid][col].set_clickable(True)
|
||||
self.listcols[grid][col].connect("clicked", self.sortcols, (col,grid))
|
||||
if col == 0:
|
||||
self.listcols[grid][col].set_sort_order(gtk.SORT_DESCENDING)
|
||||
self.listcols[grid][col].set_sort_indicator(True)
|
||||
if column[coltype] == 'cash':
|
||||
self.listcols[grid][col].set_cell_data_func(numcell, self.ledger_style_render_func)
|
||||
else:
|
||||
self.listcols[grid][col].set_cell_data_func(cellrend, self.reset_style_render_func)
|
||||
|
||||
rows = len(result) # +1 for title row
|
||||
|
||||
while sqlrow < rows:
|
||||
treerow = []
|
||||
for col,column in enumerate(self.cols_to_show):
|
||||
if column[colalias] in colnames:
|
||||
value = result[sqlrow][colnames.index(column[colalias])]
|
||||
if column[colalias] == 'plposition':
|
||||
if value == 'B':
|
||||
value = 'BB'
|
||||
elif value == 'S':
|
||||
value = 'SB'
|
||||
elif value == '0':
|
||||
value = 'Btn'
|
||||
else:
|
||||
if column[colalias] == 'game':
|
||||
if holecards:
|
||||
value = Card.twoStartCardString( result[sqlrow][hgametypeid_idx] )
|
||||
else:
|
||||
minbb = result[sqlrow][colnames.index('minbigblind')]
|
||||
maxbb = result[sqlrow][colnames.index('maxbigblind')]
|
||||
value = result[sqlrow][colnames.index('limittype')] + ' ' \
|
||||
+ result[sqlrow][colnames.index('category')].title() + ' ' \
|
||||
+ result[sqlrow][colnames.index('name')] + ' $'
|
||||
if 100 * int(minbb/100.0) != minbb:
|
||||
value += '%.2f' % (minbb/100.0)
|
||||
else:
|
||||
value += '%.0f' % (minbb/100.0)
|
||||
if minbb != maxbb:
|
||||
if 100 * int(maxbb/100.0) != maxbb:
|
||||
value += ' - $' + '%.2f' % (maxbb/100.0)
|
||||
else:
|
||||
value += ' - $' + '%.0f' % (maxbb/100.0)
|
||||
else:
|
||||
continue
|
||||
if value and value != -999:
|
||||
treerow.append(column[colformat] % value)
|
||||
else:
|
||||
treerow.append(' ')
|
||||
iter = self.liststore[grid].append(treerow)
|
||||
#print treerow
|
||||
sqlrow += 1
|
||||
row += 1
|
||||
vbox.show_all()
|
||||
|
||||
#end def addGrid(self, query, vars, playerids, sitenos, limits, type, seats, groups, dates):
|
||||
|
||||
def refineQuery(self, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games):
|
||||
having = ''
|
||||
if not flags:
|
||||
holecards = False
|
||||
numhands = 0
|
||||
else:
|
||||
holecards = flags[0]
|
||||
numhands = flags[1]
|
||||
|
||||
if 'allplayers' in groups and groups['allplayers']:
|
||||
nametest = "(hp.playerId)"
|
||||
if holecards or groups['posn']:
|
||||
pname = "'all players'"
|
||||
# set flag in self.columns to not show player name column
|
||||
[x for x in self.columns if x[0] == 'pname'][0][1] = False
|
||||
# can't do this yet (re-write doing more maths in python instead of sql?)
|
||||
if numhands:
|
||||
nametest = "(-1)"
|
||||
else:
|
||||
pname = "p.name"
|
||||
# set flag in self.columns to show player name column
|
||||
[x for x in self.columns if x[0] == 'pname'][0][1] = True
|
||||
if numhands:
|
||||
having = ' and count(1) > %d ' % (numhands,)
|
||||
else:
|
||||
if playerids:
|
||||
nametest = str(tuple(playerids))
|
||||
nametest = nametest.replace("L", "")
|
||||
nametest = nametest.replace(",)",")")
|
||||
else:
|
||||
nametest = "1 = 2"
|
||||
pname = "p.name"
|
||||
# set flag in self.columns to not show player name column
|
||||
[x for x in self.columns if x[0] == 'pname'][0][1] = False
|
||||
query = query.replace("<player_test>", nametest)
|
||||
query = query.replace("<playerName>", pname)
|
||||
query = query.replace("<havingclause>", having)
|
||||
|
||||
gametest = ""
|
||||
q = []
|
||||
for m in self.filters.display.items():
|
||||
if m[0] == 'Games' and m[1]:
|
||||
for n in games:
|
||||
if games[n]:
|
||||
q.append(n)
|
||||
if len(q) > 0:
|
||||
gametest = str(tuple(q))
|
||||
gametest = gametest.replace("L", "")
|
||||
gametest = gametest.replace(",)",")")
|
||||
gametest = gametest.replace("u'","'")
|
||||
gametest = "and gt.category in %s" % gametest
|
||||
else:
|
||||
gametest = "and gt.category IS NULL"
|
||||
query = query.replace("<game_test>", gametest)
|
||||
|
||||
sitetest = ""
|
||||
q = []
|
||||
for m in self.filters.display.items():
|
||||
if m[0] == 'Sites' and m[1]:
|
||||
for n in sitenos:
|
||||
q.append(n)
|
||||
if len(q) > 0:
|
||||
sitetest = str(tuple(q))
|
||||
sitetest = sitetest.replace("L", "")
|
||||
sitetest = sitetest.replace(",)",")")
|
||||
sitetest = sitetest.replace("u'","'")
|
||||
sitetest = "and gt.siteId in %s" % sitetest
|
||||
else:
|
||||
sitetest = "and gt.siteId IS NULL"
|
||||
query = query.replace("<site_test>", sitetest)
|
||||
|
||||
if seats:
|
||||
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
|
||||
if 'show' in seats and seats['show']:
|
||||
query = query.replace('<groupbyseats>', ',h.seats')
|
||||
query = query.replace('<orderbyseats>', ',h.seats')
|
||||
else:
|
||||
query = query.replace('<groupbyseats>', '')
|
||||
query = query.replace('<orderbyseats>', '')
|
||||
else:
|
||||
query = query.replace('<seats_test>', 'between 0 and 100')
|
||||
query = query.replace('<groupbyseats>', '')
|
||||
query = query.replace('<orderbyseats>', '')
|
||||
|
||||
lims = [int(x) for x in limits if x.isdigit()]
|
||||
potlims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'pl']
|
||||
nolims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'nl']
|
||||
bbtest = "and ( (gt.limitType = 'fl' and gt.bigBlind in "
|
||||
# and ( (limit and bb in()) or (nolimit and bb in ()) )
|
||||
if lims:
|
||||
blindtest = str(tuple(lims))
|
||||
blindtest = blindtest.replace("L", "")
|
||||
blindtest = blindtest.replace(",)",")")
|
||||
bbtest = bbtest + blindtest + ' ) '
|
||||
else:
|
||||
bbtest = bbtest + '(-1) ) '
|
||||
bbtest = bbtest + " or (gt.limitType = 'pl' and gt.bigBlind in "
|
||||
if potlims:
|
||||
blindtest = str(tuple(potlims))
|
||||
blindtest = blindtest.replace("L", "")
|
||||
blindtest = blindtest.replace(",)",")")
|
||||
bbtest = bbtest + blindtest + ' ) '
|
||||
else:
|
||||
bbtest = bbtest + '(-1) ) '
|
||||
bbtest = bbtest + " or (gt.limitType = 'nl' and gt.bigBlind in "
|
||||
if nolims:
|
||||
blindtest = str(tuple(nolims))
|
||||
blindtest = blindtest.replace("L", "")
|
||||
blindtest = blindtest.replace(",)",")")
|
||||
bbtest = bbtest + blindtest + ' ) )'
|
||||
else:
|
||||
bbtest = bbtest + '(-1) ) )'
|
||||
if type == 'ring':
|
||||
bbtest = bbtest + " and gt.type = 'ring' "
|
||||
elif type == 'tour':
|
||||
bbtest = " and gt.type = 'tour' "
|
||||
query = query.replace("<gtbigBlind_test>", bbtest)
|
||||
|
||||
if holecards: # re-use level variables for hole card query
|
||||
query = query.replace("<hgameTypeId>", "hp.startcards")
|
||||
query = query.replace("<orderbyhgameTypeId>"
|
||||
, ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 "
|
||||
+ " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 "
|
||||
+ " end desc ")
|
||||
else:
|
||||
query = query.replace("<orderbyhgameTypeId>", "")
|
||||
groupLevels = "show" not in str(limits)
|
||||
if groupLevels:
|
||||
query = query.replace("<hgameTypeId>", "p.name") # need to use p.name for sqlite posn stats to work
|
||||
else:
|
||||
query = query.replace("<hgameTypeId>", "h.gameTypeId")
|
||||
|
||||
# process self.detailFilters (a list of tuples)
|
||||
flagtest = ''
|
||||
#self.detailFilters = [('h.seats', 5, 6)] # for debug
|
||||
if self.detailFilters:
|
||||
for f in self.detailFilters:
|
||||
if len(f) == 3:
|
||||
# X between Y and Z
|
||||
flagtest += ' and %s between %s and %s ' % (f[0], str(f[1]), str(f[2]))
|
||||
query = query.replace("<flagtest>", flagtest)
|
||||
|
||||
# allow for differences in sql cast() function:
|
||||
if self.db.backend == self.MYSQL_INNODB:
|
||||
query = query.replace("<signed>", 'signed ')
|
||||
else:
|
||||
query = query.replace("<signed>", '')
|
||||
|
||||
# Filter on dates
|
||||
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
|
||||
|
||||
# Group by position?
|
||||
if groups['posn']:
|
||||
#query = query.replace("<position>", "case hp.position when '0' then 'Btn' else hp.position end")
|
||||
query = query.replace("<position>", "hp.position")
|
||||
# set flag in self.columns to show posn column
|
||||
[x for x in self.columns if x[0] == 'plposition'][0][1] = True
|
||||
else:
|
||||
query = query.replace("<position>", "gt.base")
|
||||
# unset flag in self.columns to hide posn column
|
||||
[x for x in self.columns if x[0] == 'plposition'][0][1] = False
|
||||
|
||||
#print "query =\n", query
|
||||
return(query)
|
||||
#end def refineQuery(self, query, playerids, sitenos, limits):
|
||||
|
||||
def showDetailFilter(self, widget, data):
|
||||
detailDialog = gtk.Dialog(title="Detailed Filters", parent=self.main_window
|
||||
,flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
|
||||
,buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
|
||||
|
||||
handbox = gtk.VBox(True, 0)
|
||||
detailDialog.vbox.pack_start(handbox, False, False, 0)
|
||||
handbox.show()
|
||||
|
||||
label = gtk.Label("Hand Filters:")
|
||||
handbox.add(label)
|
||||
label.show()
|
||||
|
||||
betweenFilters = []
|
||||
for htest in self.handtests:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
handbox.pack_start(hbox, False, False, 0)
|
||||
hbox.show()
|
||||
|
||||
cb = gtk.CheckButton()
|
||||
lbl_from = gtk.Label(htest[1])
|
||||
lbl_from.set_alignment(xalign=0.0, yalign=0.5)
|
||||
lbl_tween = gtk.Label('between')
|
||||
lbl_to = gtk.Label('and')
|
||||
adj1 = gtk.Adjustment(value=htest[2], lower=0, upper=10, step_incr=1, page_incr=1, page_size=0)
|
||||
sb1 = gtk.SpinButton(adjustment=adj1, climb_rate=0.0, digits=0)
|
||||
adj2 = gtk.Adjustment(value=htest[3], lower=2, upper=10, step_incr=1, page_incr=1, page_size=0)
|
||||
sb2 = gtk.SpinButton(adjustment=adj2, climb_rate=0.0, digits=0)
|
||||
|
||||
for df in [x for x in self.detailFilters if x[0] == htest[0]]:
|
||||
cb.set_active(True)
|
||||
|
||||
hbox.pack_start(cb, expand=False, padding=3)
|
||||
hbox.pack_start(lbl_from, expand=True, padding=3)
|
||||
hbox.pack_start(lbl_tween, expand=False, padding=3)
|
||||
hbox.pack_start(sb1, False, False, 0)
|
||||
hbox.pack_start(lbl_to, expand=False, padding=3)
|
||||
hbox.pack_start(sb2, False, False, 0)
|
||||
|
||||
cb.show()
|
||||
lbl_from.show()
|
||||
lbl_tween.show()
|
||||
sb1.show()
|
||||
lbl_to.show()
|
||||
sb2.show()
|
||||
|
||||
htest[4:7] = [cb,sb1,sb2]
|
||||
|
||||
response = detailDialog.run()
|
||||
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
self.detailFilters = []
|
||||
for ht in self.handtests:
|
||||
if ht[4].get_active():
|
||||
self.detailFilters.append( (ht[0], ht[5].get_value_as_int(), ht[6].get_value_as_int()) )
|
||||
ht[2],ht[3] = ht[5].get_value_as_int(), ht[6].get_value_as_int()
|
||||
print "detailFilters =", self.detailFilters
|
||||
self.refreshStats(None, None)
|
||||
|
||||
detailDialog.destroy()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class GuiPlayerStats(threading.Thread):
|
||||
pass
|
||||
#end class GuiPlayerStats
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -25,7 +25,6 @@ from time import time, strftime
|
|||
import fpdb_import
|
||||
import Database
|
||||
import Filters
|
||||
import FpdbSQLQueries
|
||||
|
||||
class GuiPositionalStats (threading.Thread):
|
||||
def __init__(self, config, querylist, debug=True):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
|
681
pyfpdb/GuiRingPlayerStats.py
Normal file
681
pyfpdb/GuiRingPlayerStats.py
Normal file
|
@ -0,0 +1,681 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
import traceback
|
||||
import threading
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import os
|
||||
import sys
|
||||
from time import time, strftime
|
||||
|
||||
import Card
|
||||
import fpdb_import
|
||||
import Database
|
||||
import Filters
|
||||
import Charset
|
||||
import GuiPlayerStats
|
||||
|
||||
colalias,colshow,colheading,colxalign,colformat,coltype = 0,1,2,3,4,5
|
||||
ranks = {'x':0, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, 'T':10, 'J':11, 'Q':12, 'K':13, 'A':14}
|
||||
|
||||
class GuiRingPlayerStats (GuiPlayerStats.GuiPlayerStats):
|
||||
|
||||
def __init__(self, config, querylist, mainwin, debug=True):
|
||||
self.debug = debug
|
||||
self.conf = config
|
||||
self.main_window = mainwin
|
||||
self.sql = querylist
|
||||
|
||||
self.liststore = [] # gtk.ListStore[] stores the contents of the grids
|
||||
self.listcols = [] # gtk.TreeViewColumn[][] stores the columns in the grids
|
||||
|
||||
self.MYSQL_INNODB = 2
|
||||
self.PGSQL = 3
|
||||
self.SQLITE = 4
|
||||
|
||||
# create new db connection to avoid conflicts with other threads
|
||||
self.db = Database.Database(self.conf, sql=self.sql)
|
||||
self.cursor = self.db.cursor
|
||||
|
||||
settings = {}
|
||||
settings.update(self.conf.get_db_parameters())
|
||||
settings.update(self.conf.get_tv_parameters())
|
||||
settings.update(self.conf.get_import_parameters())
|
||||
settings.update(self.conf.get_default_paths())
|
||||
|
||||
# text used on screen stored here so that it can be configured
|
||||
self.filterText = {'handhead':'Hand Breakdown for all levels listed above'
|
||||
}
|
||||
|
||||
filters_display = { "Heroes" : True,
|
||||
"Sites" : True,
|
||||
"Games" : True,
|
||||
"Limits" : True,
|
||||
"LimitSep" : True,
|
||||
"LimitType" : True,
|
||||
"Type" : True,
|
||||
"Seats" : True,
|
||||
"SeatSep" : True,
|
||||
"Dates" : True,
|
||||
"Groups" : True,
|
||||
"GroupsAll" : True,
|
||||
"Button1" : True,
|
||||
"Button2" : True
|
||||
}
|
||||
|
||||
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
|
||||
self.filters.registerButton1Name("_Filters")
|
||||
self.filters.registerButton1Callback(self.showDetailFilter)
|
||||
self.filters.registerButton2Name("_Refresh Stats")
|
||||
self.filters.registerButton2Callback(self.refreshStats)
|
||||
|
||||
# ToDo: store in config
|
||||
# ToDo: create popup to adjust column config
|
||||
# columns to display, keys match column name returned by sql, values in tuple are:
|
||||
# is column displayed, column heading, xalignment, formatting, celltype
|
||||
self.columns = [ ["game", True, "Game", 0.0, "%s", "str"]
|
||||
, ["hand", False, "Hand", 0.0, "%s", "str"] # true not allowed for this line
|
||||
, ["plposition", False, "Posn", 1.0, "%s", "str"] # true not allowed for this line (set in code)
|
||||
, ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code)
|
||||
, ["n", True, "Hds", 1.0, "%1.0f", "str"]
|
||||
, ["avgseats", False, "Seats", 1.0, "%3.1f", "str"]
|
||||
, ["vpip", True, "VPIP", 1.0, "%3.1f", "str"]
|
||||
, ["pfr", True, "PFR", 1.0, "%3.1f", "str"]
|
||||
, ["pf3", True, "PF3", 1.0, "%3.1f", "str"]
|
||||
, ["aggfac", True, "AggFac", 1.0, "%2.2f", "str"]
|
||||
, ["aggfrq", True, "AggFreq", 1.0, "%3.1f", "str"]
|
||||
, ["conbet", True, "ContBet", 1.0, "%3.1f", "str"]
|
||||
, ["steals", True, "Steals", 1.0, "%3.1f", "str"]
|
||||
, ["saw_f", True, "Saw_F", 1.0, "%3.1f", "str"]
|
||||
, ["sawsd", True, "SawSD", 1.0, "%3.1f", "str"]
|
||||
, ["wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f", "str"]
|
||||
, ["wmsd", True, "W$SD", 1.0, "%3.1f", "str"]
|
||||
, ["flafq", True, "FlAFq", 1.0, "%3.1f", "str"]
|
||||
, ["tuafq", True, "TuAFq", 1.0, "%3.1f", "str"]
|
||||
, ["rvafq", True, "RvAFq", 1.0, "%3.1f", "str"]
|
||||
, ["pofafq", False, "PoFAFq", 1.0, "%3.1f", "str"]
|
||||
, ["net", True, "Net($)", 1.0, "%6.2f", "cash"]
|
||||
, ["bbper100", True, "bb/100", 1.0, "%4.2f", "str"]
|
||||
, ["rake", True, "Rake($)", 1.0, "%6.2f", "cash"]
|
||||
, ["bb100xr", True, "bbxr/100", 1.0, "%4.2f", "str"]
|
||||
, ["variance", True, "Variance", 1.0, "%5.2f", "str"]
|
||||
]
|
||||
|
||||
# Detail filters: This holds the data used in the popup window, extra values are
|
||||
# added at the end of these lists during processing
|
||||
# sql test, screen description, min, max
|
||||
self.handtests = [ # already in filter class : ['h.seats', 'Number of Players', 2, 10]
|
||||
['h.maxSeats', 'Size of Table', 2, 10]
|
||||
,['h.playersVpi', 'Players who VPI', 0, 10]
|
||||
,['h.playersAtStreet1', 'Players at Flop', 0, 10]
|
||||
,['h.playersAtStreet2', 'Players at Turn', 0, 10]
|
||||
,['h.playersAtStreet3', 'Players at River', 0, 10]
|
||||
,['h.playersAtStreet4', 'Players at Street7', 0, 10]
|
||||
,['h.playersAtShowdown', 'Players at Showdown', 0, 10]
|
||||
,['h.street0Raises', 'Bets to See Flop', 0, 5]
|
||||
,['h.street1Raises', 'Bets to See Turn', 0, 5]
|
||||
,['h.street2Raises', 'Bets to See River', 0, 5]
|
||||
,['h.street3Raises', 'Bets to See Street7', 0, 5]
|
||||
,['h.street4Raises', 'Bets to See Showdown', 0, 5]
|
||||
]
|
||||
|
||||
self.stats_frame = None
|
||||
self.stats_vbox = None
|
||||
self.detailFilters = [] # the data used to enhance the sql select
|
||||
|
||||
#self.main_hbox = gtk.HBox(False, 0)
|
||||
#self.main_hbox.show()
|
||||
self.main_hbox = gtk.HPaned()
|
||||
|
||||
self.stats_frame = gtk.Frame()
|
||||
self.stats_frame.show()
|
||||
|
||||
self.stats_vbox = gtk.VPaned()
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
# self.fillStatsFrame(self.stats_vbox)
|
||||
|
||||
#self.main_hbox.pack_start(self.filters.get_vbox())
|
||||
#self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
|
||||
self.main_hbox.pack1(self.filters.get_vbox())
|
||||
self.main_hbox.pack2(self.stats_frame)
|
||||
self.main_hbox.show()
|
||||
|
||||
# make sure Hand column is not displayed
|
||||
[x for x in self.columns if x[0] == 'hand'][0][1] = False
|
||||
self.last_pos = -1
|
||||
|
||||
|
||||
def get_vbox(self):
|
||||
"""returns the vbox of this thread"""
|
||||
return self.main_hbox
|
||||
#end def get_vbox
|
||||
|
||||
def refreshStats(self, widget, data):
|
||||
self.last_pos = self.stats_vbox.get_position()
|
||||
try: self.stats_vbox.destroy()
|
||||
except AttributeError: pass
|
||||
self.liststore = []
|
||||
self.listcols = []
|
||||
#self.stats_vbox = gtk.VBox(False, 0)
|
||||
self.stats_vbox = gtk.VPaned()
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
self.fillStatsFrame(self.stats_vbox)
|
||||
if self.last_pos > 0:
|
||||
self.stats_vbox.set_position(self.last_pos)
|
||||
#end def refreshStats
|
||||
|
||||
def fillStatsFrame(self, vbox):
|
||||
sites = self.filters.getSites()
|
||||
heroes = self.filters.getHeroes()
|
||||
siteids = self.filters.getSiteIds()
|
||||
limits = self.filters.getLimits()
|
||||
type = self.filters.getType()
|
||||
seats = self.filters.getSeats()
|
||||
groups = self.filters.getGroups()
|
||||
dates = self.filters.getDates()
|
||||
games = self.filters.getGames()
|
||||
sitenos = []
|
||||
playerids = []
|
||||
|
||||
# Which sites are selected?
|
||||
for site in sites:
|
||||
if sites[site] == True:
|
||||
sitenos.append(siteids[site])
|
||||
_hname = Charset.to_utf8(heroes[site])
|
||||
result = self.db.get_player_id(self.conf, site, _hname)
|
||||
if result is not None:
|
||||
playerids.append(int(result))
|
||||
|
||||
if not sitenos:
|
||||
#Should probably pop up here.
|
||||
print "No sites selected - defaulting to PokerStars"
|
||||
sitenos = [2]
|
||||
if not playerids:
|
||||
print "No player ids found"
|
||||
return
|
||||
if not limits:
|
||||
print "No limits found"
|
||||
return
|
||||
|
||||
self.createStatsTable(vbox, playerids, sitenos, limits, type, seats, groups, dates, games)
|
||||
#end def fillStatsFrame
|
||||
|
||||
def createStatsTable(self, vbox, playerids, sitenos, limits, type, seats, groups, dates, games):
|
||||
startTime = time()
|
||||
show_detail = True
|
||||
|
||||
# Scrolled window for summary table
|
||||
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
||||
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
swin.show()
|
||||
vbox.pack1(swin)
|
||||
|
||||
# Display summary table at top of page
|
||||
# 3rd parameter passes extra flags, currently includes:
|
||||
# holecards - whether to display card breakdown (True/False)
|
||||
# numhands - min number hands required when displaying all players
|
||||
# gridnum - index for grid data structures
|
||||
flags = [False, self.filters.getNumHands(), 0]
|
||||
self.addGrid(swin, 'playerDetailedStats', flags, playerids
|
||||
,sitenos, limits, type, seats, groups, dates, games)
|
||||
|
||||
if 'allplayers' in groups and groups['allplayers']:
|
||||
# can't currently do this combination so skip detailed table
|
||||
show_detail = False
|
||||
|
||||
if show_detail:
|
||||
# Separator
|
||||
vbox2 = gtk.VBox(False, 0)
|
||||
heading = gtk.Label(self.filterText['handhead'])
|
||||
heading.show()
|
||||
vbox2.pack_start(heading, expand=False, padding=3)
|
||||
|
||||
# Scrolled window for detailed table (display by hand)
|
||||
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
||||
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
swin.show()
|
||||
vbox2.pack_start(swin, expand=True, padding=3)
|
||||
vbox.pack2(vbox2)
|
||||
vbox2.show()
|
||||
|
||||
# Detailed table
|
||||
flags[0] = True
|
||||
flags[2] = 1
|
||||
self.addGrid(swin, 'playerDetailedStats', flags, playerids
|
||||
,sitenos, limits, type, seats, groups, dates, games)
|
||||
|
||||
self.db.rollback()
|
||||
print "Stats page displayed in %4.2f seconds" % (time() - startTime)
|
||||
#end def createStatsTable
|
||||
|
||||
def reset_style_render_func(self, treeviewcolumn, cell, model, iter):
|
||||
cell.set_property('foreground', 'black')
|
||||
#end def reset_style_render_func
|
||||
|
||||
def ledger_style_render_func(self, tvcol, cell, model, iter):
|
||||
str = cell.get_property('text')
|
||||
if '-' in str:
|
||||
str = str.replace("-", "")
|
||||
str = "(%s)" %(str)
|
||||
cell.set_property('text', str)
|
||||
cell.set_property('foreground', 'red')
|
||||
else:
|
||||
cell.set_property('foreground', 'darkgreen')
|
||||
|
||||
return
|
||||
|
||||
def sortnums(self, model, iter1, iter2, nums):
|
||||
try:
|
||||
ret = 0
|
||||
(n, grid) = nums
|
||||
a = self.liststore[grid].get_value(iter1, n)
|
||||
b = self.liststore[grid].get_value(iter2, n)
|
||||
if 'f' in self.cols_to_show[n][4]:
|
||||
try: a = float(a)
|
||||
except: a = 0.0
|
||||
try: b = float(b)
|
||||
except: b = 0.0
|
||||
if n == 0 and grid == 1: #make sure it only works on the starting hands
|
||||
a1,a2,a3 = ranks[a[0]], ranks[a[1]], (a+'o')[2]
|
||||
b1,b2,b3 = ranks[b[0]], ranks[b[1]], (b+'o')[2]
|
||||
if a1 > b1 or ( a1 == b1 and (a2 > b2 or (a2 == b2 and a3 > b3) ) ):
|
||||
ret = 1
|
||||
else:
|
||||
ret = -1
|
||||
else:
|
||||
if a < b:
|
||||
ret = -1
|
||||
elif a == b:
|
||||
ret = 0
|
||||
else:
|
||||
ret = 1
|
||||
#print "n =", n, "iter1[n] =", self.liststore[grid].get_value(iter1,n), "iter2[n] =", self.liststore[grid].get_value(iter2,n), "ret =", ret
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])
|
||||
print "***sortnums error: " + str(sys.exc_info()[1])
|
||||
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
|
||||
|
||||
return(ret)
|
||||
|
||||
def sortcols(self, col, nums):
|
||||
try:
|
||||
#This doesn't actually work yet - clicking heading in top section sorts bottom section :-(
|
||||
(n, grid) = nums
|
||||
if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
|
||||
col.set_sort_order(gtk.SORT_DESCENDING)
|
||||
else:
|
||||
col.set_sort_order(gtk.SORT_ASCENDING)
|
||||
self.liststore[grid].set_sort_column_id(n, col.get_sort_order())
|
||||
self.liststore[grid].set_sort_func(n, self.sortnums, (n,grid))
|
||||
for i in xrange(len(self.listcols[grid])):
|
||||
self.listcols[grid][i].set_sort_indicator(False)
|
||||
self.listcols[grid][n].set_sort_indicator(True)
|
||||
# use this listcols[col].set_sort_indicator(True)
|
||||
# to turn indicator off for other cols
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])
|
||||
print "***sortcols error: " + str(sys.exc_info()[1])
|
||||
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
|
||||
#end def sortcols
|
||||
|
||||
def addGrid(self, vbox, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games):
|
||||
counter = 0
|
||||
row = 0
|
||||
sqlrow = 0
|
||||
if not flags: holecards,grid = False,0
|
||||
else: holecards,grid = flags[0],flags[2]
|
||||
|
||||
tmp = self.sql.query[query]
|
||||
tmp = self.refineQuery(tmp, flags, playerids, sitenos, limits, type, seats, groups, dates, games)
|
||||
self.cursor.execute(tmp)
|
||||
result = self.cursor.fetchall()
|
||||
colnames = [desc[0].lower() for desc in self.cursor.description]
|
||||
|
||||
# pre-fetch some constant values:
|
||||
self.cols_to_show = [x for x in self.columns if x[colshow]]
|
||||
hgametypeid_idx = colnames.index('hgametypeid')
|
||||
|
||||
assert len(self.liststore) == grid, "len(self.liststore)="+str(len(self.liststore))+" grid-1="+str(grid)
|
||||
self.liststore.append( gtk.ListStore(*([str] * len(self.cols_to_show))) )
|
||||
view = gtk.TreeView(model=self.liststore[grid])
|
||||
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
|
||||
#vbox.pack_start(view, expand=False, padding=3)
|
||||
vbox.add(view)
|
||||
textcell = gtk.CellRendererText()
|
||||
textcell50 = gtk.CellRendererText()
|
||||
textcell50.set_property('xalign', 0.5)
|
||||
numcell = gtk.CellRendererText()
|
||||
numcell.set_property('xalign', 1.0)
|
||||
assert len(self.listcols) == grid
|
||||
self.listcols.append( [] )
|
||||
|
||||
# Create header row eg column: ("game", True, "Game", 0.0, "%s")
|
||||
for col, column in enumerate(self.cols_to_show):
|
||||
if column[colalias] == 'game' and holecards:
|
||||
s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
|
||||
else:
|
||||
s = column[colheading]
|
||||
self.listcols[grid].append(gtk.TreeViewColumn(s))
|
||||
view.append_column(self.listcols[grid][col])
|
||||
if column[colformat] == '%s':
|
||||
if column[colxalign] == 0.0:
|
||||
self.listcols[grid][col].pack_start(textcell, expand=True)
|
||||
self.listcols[grid][col].add_attribute(textcell, 'text', col)
|
||||
cellrend = textcell
|
||||
else:
|
||||
self.listcols[grid][col].pack_start(textcell50, expand=True)
|
||||
self.listcols[grid][col].add_attribute(textcell50, 'text', col)
|
||||
cellrend = textcell50
|
||||
self.listcols[grid][col].set_expand(True)
|
||||
else:
|
||||
self.listcols[grid][col].pack_start(numcell, expand=True)
|
||||
self.listcols[grid][col].add_attribute(numcell, 'text', col)
|
||||
self.listcols[grid][col].set_expand(True)
|
||||
cellrend = numcell
|
||||
#self.listcols[grid][col].set_alignment(column[colxalign]) # no effect?
|
||||
self.listcols[grid][col].set_clickable(True)
|
||||
self.listcols[grid][col].connect("clicked", self.sortcols, (col,grid))
|
||||
if col == 0:
|
||||
self.listcols[grid][col].set_sort_order(gtk.SORT_DESCENDING)
|
||||
self.listcols[grid][col].set_sort_indicator(True)
|
||||
if column[coltype] == 'cash':
|
||||
self.listcols[grid][col].set_cell_data_func(numcell, self.ledger_style_render_func)
|
||||
else:
|
||||
self.listcols[grid][col].set_cell_data_func(cellrend, self.reset_style_render_func)
|
||||
|
||||
rows = len(result) # +1 for title row
|
||||
|
||||
while sqlrow < rows:
|
||||
treerow = []
|
||||
for col,column in enumerate(self.cols_to_show):
|
||||
if column[colalias] in colnames:
|
||||
value = result[sqlrow][colnames.index(column[colalias])]
|
||||
if column[colalias] == 'plposition':
|
||||
if value == 'B':
|
||||
value = 'BB'
|
||||
elif value == 'S':
|
||||
value = 'SB'
|
||||
elif value == '0':
|
||||
value = 'Btn'
|
||||
else:
|
||||
if column[colalias] == 'game':
|
||||
if holecards:
|
||||
value = Card.twoStartCardString( result[sqlrow][hgametypeid_idx] )
|
||||
else:
|
||||
minbb = result[sqlrow][colnames.index('minbigblind')]
|
||||
maxbb = result[sqlrow][colnames.index('maxbigblind')]
|
||||
value = result[sqlrow][colnames.index('limittype')] + ' ' \
|
||||
+ result[sqlrow][colnames.index('category')].title() + ' ' \
|
||||
+ result[sqlrow][colnames.index('name')] + ' $'
|
||||
if 100 * int(minbb/100.0) != minbb:
|
||||
value += '%.2f' % (minbb/100.0)
|
||||
else:
|
||||
value += '%.0f' % (minbb/100.0)
|
||||
if minbb != maxbb:
|
||||
if 100 * int(maxbb/100.0) != maxbb:
|
||||
value += ' - $' + '%.2f' % (maxbb/100.0)
|
||||
else:
|
||||
value += ' - $' + '%.0f' % (maxbb/100.0)
|
||||
else:
|
||||
continue
|
||||
if value and value != -999:
|
||||
treerow.append(column[colformat] % value)
|
||||
else:
|
||||
treerow.append(' ')
|
||||
iter = self.liststore[grid].append(treerow)
|
||||
#print treerow
|
||||
sqlrow += 1
|
||||
row += 1
|
||||
vbox.show_all()
|
||||
#end def addGrid
|
||||
|
||||
def refineQuery(self, query, flags, playerids, sitenos, limits, type, seats, groups, dates, games):
|
||||
having = ''
|
||||
if not flags:
|
||||
holecards = False
|
||||
numhands = 0
|
||||
else:
|
||||
holecards = flags[0]
|
||||
numhands = flags[1]
|
||||
|
||||
if 'allplayers' in groups and groups['allplayers']:
|
||||
nametest = "(hp.playerId)"
|
||||
if holecards or groups['posn']:
|
||||
pname = "'all players'"
|
||||
# set flag in self.columns to not show player name column
|
||||
[x for x in self.columns if x[0] == 'pname'][0][1] = False
|
||||
# can't do this yet (re-write doing more maths in python instead of sql?)
|
||||
if numhands:
|
||||
nametest = "(-1)"
|
||||
else:
|
||||
pname = "p.name"
|
||||
# set flag in self.columns to show player name column
|
||||
[x for x in self.columns if x[0] == 'pname'][0][1] = True
|
||||
if numhands:
|
||||
having = ' and count(1) > %d ' % (numhands,)
|
||||
else:
|
||||
if playerids:
|
||||
nametest = str(tuple(playerids))
|
||||
nametest = nametest.replace("L", "")
|
||||
nametest = nametest.replace(",)",")")
|
||||
else:
|
||||
nametest = "1 = 2"
|
||||
pname = "p.name"
|
||||
# set flag in self.columns to not show player name column
|
||||
[x for x in self.columns if x[0] == 'pname'][0][1] = False
|
||||
query = query.replace("<player_test>", nametest)
|
||||
query = query.replace("<playerName>", pname)
|
||||
query = query.replace("<havingclause>", having)
|
||||
|
||||
gametest = ""
|
||||
q = []
|
||||
for m in self.filters.display.items():
|
||||
if m[0] == 'Games' and m[1]:
|
||||
for n in games:
|
||||
if games[n]:
|
||||
q.append(n)
|
||||
if len(q) > 0:
|
||||
gametest = str(tuple(q))
|
||||
gametest = gametest.replace("L", "")
|
||||
gametest = gametest.replace(",)",")")
|
||||
gametest = gametest.replace("u'","'")
|
||||
gametest = "and gt.category in %s" % gametest
|
||||
else:
|
||||
gametest = "and gt.category IS NULL"
|
||||
query = query.replace("<game_test>", gametest)
|
||||
|
||||
sitetest = ""
|
||||
q = []
|
||||
for m in self.filters.display.items():
|
||||
if m[0] == 'Sites' and m[1]:
|
||||
for n in sitenos:
|
||||
q.append(n)
|
||||
if len(q) > 0:
|
||||
sitetest = str(tuple(q))
|
||||
sitetest = sitetest.replace("L", "")
|
||||
sitetest = sitetest.replace(",)",")")
|
||||
sitetest = sitetest.replace("u'","'")
|
||||
sitetest = "and gt.siteId in %s" % sitetest
|
||||
else:
|
||||
sitetest = "and gt.siteId IS NULL"
|
||||
query = query.replace("<site_test>", sitetest)
|
||||
|
||||
if seats:
|
||||
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
|
||||
if 'show' in seats and seats['show']:
|
||||
query = query.replace('<groupbyseats>', ',h.seats')
|
||||
query = query.replace('<orderbyseats>', ',h.seats')
|
||||
else:
|
||||
query = query.replace('<groupbyseats>', '')
|
||||
query = query.replace('<orderbyseats>', '')
|
||||
else:
|
||||
query = query.replace('<seats_test>', 'between 0 and 100')
|
||||
query = query.replace('<groupbyseats>', '')
|
||||
query = query.replace('<orderbyseats>', '')
|
||||
|
||||
lims = [int(x) for x in limits if x.isdigit()]
|
||||
potlims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'pl']
|
||||
nolims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'nl']
|
||||
bbtest = "and ( (gt.limitType = 'fl' and gt.bigBlind in "
|
||||
# and ( (limit and bb in()) or (nolimit and bb in ()) )
|
||||
if lims:
|
||||
blindtest = str(tuple(lims))
|
||||
blindtest = blindtest.replace("L", "")
|
||||
blindtest = blindtest.replace(",)",")")
|
||||
bbtest = bbtest + blindtest + ' ) '
|
||||
else:
|
||||
bbtest = bbtest + '(-1) ) '
|
||||
bbtest = bbtest + " or (gt.limitType = 'pl' and gt.bigBlind in "
|
||||
if potlims:
|
||||
blindtest = str(tuple(potlims))
|
||||
blindtest = blindtest.replace("L", "")
|
||||
blindtest = blindtest.replace(",)",")")
|
||||
bbtest = bbtest + blindtest + ' ) '
|
||||
else:
|
||||
bbtest = bbtest + '(-1) ) '
|
||||
bbtest = bbtest + " or (gt.limitType = 'nl' and gt.bigBlind in "
|
||||
if nolims:
|
||||
blindtest = str(tuple(nolims))
|
||||
blindtest = blindtest.replace("L", "")
|
||||
blindtest = blindtest.replace(",)",")")
|
||||
bbtest = bbtest + blindtest + ' ) )'
|
||||
else:
|
||||
bbtest = bbtest + '(-1) ) )'
|
||||
if type == 'ring':
|
||||
bbtest = bbtest + " and gt.type = 'ring' "
|
||||
elif type == 'tour':
|
||||
bbtest = " and gt.type = 'tour' "
|
||||
query = query.replace("<gtbigBlind_test>", bbtest)
|
||||
|
||||
if holecards: # re-use level variables for hole card query
|
||||
query = query.replace("<hgameTypeId>", "hp.startcards")
|
||||
query = query.replace("<orderbyhgameTypeId>"
|
||||
, ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 "
|
||||
+ " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 "
|
||||
+ " end desc ")
|
||||
else:
|
||||
query = query.replace("<orderbyhgameTypeId>", "")
|
||||
groupLevels = "show" not in str(limits)
|
||||
if groupLevels:
|
||||
query = query.replace("<hgameTypeId>", "p.name") # need to use p.name for sqlite posn stats to work
|
||||
else:
|
||||
query = query.replace("<hgameTypeId>", "h.gameTypeId")
|
||||
|
||||
# process self.detailFilters (a list of tuples)
|
||||
flagtest = ''
|
||||
#self.detailFilters = [('h.seats', 5, 6)] # for debug
|
||||
if self.detailFilters:
|
||||
for f in self.detailFilters:
|
||||
if len(f) == 3:
|
||||
# X between Y and Z
|
||||
flagtest += ' and %s between %s and %s ' % (f[0], str(f[1]), str(f[2]))
|
||||
query = query.replace("<flagtest>", flagtest)
|
||||
|
||||
# allow for differences in sql cast() function:
|
||||
if self.db.backend == self.MYSQL_INNODB:
|
||||
query = query.replace("<signed>", 'signed ')
|
||||
else:
|
||||
query = query.replace("<signed>", '')
|
||||
|
||||
# Filter on dates
|
||||
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
|
||||
|
||||
# Group by position?
|
||||
if groups['posn']:
|
||||
#query = query.replace("<position>", "case hp.position when '0' then 'Btn' else hp.position end")
|
||||
query = query.replace("<position>", "hp.position")
|
||||
# set flag in self.columns to show posn column
|
||||
[x for x in self.columns if x[0] == 'plposition'][0][1] = True
|
||||
else:
|
||||
query = query.replace("<position>", "gt.base")
|
||||
# unset flag in self.columns to hide posn column
|
||||
[x for x in self.columns if x[0] == 'plposition'][0][1] = False
|
||||
|
||||
#print "query =\n", query
|
||||
return(query)
|
||||
#end def refineQuery
|
||||
|
||||
def showDetailFilter(self, widget, data):
|
||||
detailDialog = gtk.Dialog(title="Detailed Filters", parent=self.main_window
|
||||
,flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
|
||||
,buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
|
||||
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
|
||||
|
||||
handbox = gtk.VBox(True, 0)
|
||||
detailDialog.vbox.pack_start(handbox, False, False, 0)
|
||||
handbox.show()
|
||||
|
||||
label = gtk.Label("Hand Filters:")
|
||||
handbox.add(label)
|
||||
label.show()
|
||||
|
||||
betweenFilters = []
|
||||
for htest in self.handtests:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
handbox.pack_start(hbox, False, False, 0)
|
||||
hbox.show()
|
||||
|
||||
cb = gtk.CheckButton()
|
||||
lbl_from = gtk.Label(htest[1])
|
||||
lbl_from.set_alignment(xalign=0.0, yalign=0.5)
|
||||
lbl_tween = gtk.Label('between')
|
||||
lbl_to = gtk.Label('and')
|
||||
adj1 = gtk.Adjustment(value=htest[2], lower=0, upper=10, step_incr=1, page_incr=1, page_size=0)
|
||||
sb1 = gtk.SpinButton(adjustment=adj1, climb_rate=0.0, digits=0)
|
||||
adj2 = gtk.Adjustment(value=htest[3], lower=2, upper=10, step_incr=1, page_incr=1, page_size=0)
|
||||
sb2 = gtk.SpinButton(adjustment=adj2, climb_rate=0.0, digits=0)
|
||||
|
||||
for df in [x for x in self.detailFilters if x[0] == htest[0]]:
|
||||
cb.set_active(True)
|
||||
|
||||
hbox.pack_start(cb, expand=False, padding=3)
|
||||
hbox.pack_start(lbl_from, expand=True, padding=3)
|
||||
hbox.pack_start(lbl_tween, expand=False, padding=3)
|
||||
hbox.pack_start(sb1, False, False, 0)
|
||||
hbox.pack_start(lbl_to, expand=False, padding=3)
|
||||
hbox.pack_start(sb2, False, False, 0)
|
||||
|
||||
cb.show()
|
||||
lbl_from.show()
|
||||
lbl_tween.show()
|
||||
sb1.show()
|
||||
lbl_to.show()
|
||||
sb2.show()
|
||||
|
||||
htest[4:7] = [cb,sb1,sb2]
|
||||
|
||||
response = detailDialog.run()
|
||||
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
self.detailFilters = []
|
||||
for ht in self.handtests:
|
||||
if ht[4].get_active():
|
||||
self.detailFilters.append( (ht[0], ht[5].get_value_as_int(), ht[6].get_value_as_int()) )
|
||||
ht[2],ht[3] = ht[5].get_value_as_int(), ht[6].get_value_as_int()
|
||||
print "detailFilters =", self.detailFilters
|
||||
self.refreshStats(None, None)
|
||||
|
||||
detailDialog.destroy()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -46,7 +46,6 @@ import Card
|
|||
import fpdb_import
|
||||
import Database
|
||||
import Filters
|
||||
import FpdbSQLQueries
|
||||
import Charset
|
||||
|
||||
class GuiSessionViewer (threading.Thread):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
|
457
pyfpdb/GuiTourneyPlayerStats.py
Normal file
457
pyfpdb/GuiTourneyPlayerStats.py
Normal file
|
@ -0,0 +1,457 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
#import traceback
|
||||
import threading
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
#import os
|
||||
#import sys
|
||||
from time import time, strftime
|
||||
|
||||
#import Card
|
||||
#import fpdb_import
|
||||
#import Database
|
||||
import Charset
|
||||
import TourneyFilters
|
||||
import GuiPlayerStats
|
||||
|
||||
colalias,colshow,colheading,colxalign,colformat,coltype = 0,1,2,3,4,5
|
||||
|
||||
class GuiTourneyPlayerStats (GuiPlayerStats.GuiPlayerStats):
|
||||
def __init__(self, config, db, sql, mainwin, debug=True):
|
||||
self.conf = config
|
||||
self.db = db
|
||||
self.cursor = self.db.cursor
|
||||
self.sql = sql
|
||||
self.main_window = mainwin
|
||||
self.debug = debug
|
||||
|
||||
self.liststore = [] # gtk.ListStore[] stores the contents of the grids
|
||||
self.listcols = [] # gtk.TreeViewColumn[][] stores the columns in the grids
|
||||
|
||||
filters_display = { "Heroes" : True,
|
||||
"Sites" : True,
|
||||
#"Games" : True,
|
||||
#"Limits" : True,
|
||||
#"LimitSep" : True,
|
||||
#"LimitType" : True,
|
||||
#"Type" : True,
|
||||
"Seats" : True,
|
||||
#"SeatSep" : True,
|
||||
"Dates" : True,
|
||||
#"Groups" : True,
|
||||
#"GroupsAll" : True,
|
||||
#"Button1" : True,
|
||||
"Button2" : True}
|
||||
|
||||
self.stats_frame = None
|
||||
self.stats_vbox = None
|
||||
self.detailFilters = [] # the data used to enhance the sql select
|
||||
|
||||
self.main_hbox = gtk.HPaned()
|
||||
|
||||
self.filters = TourneyFilters.TourneyFilters(self.db, self.conf, self.sql, display = filters_display)
|
||||
#self.filters.registerButton1Name("_Filters")
|
||||
#self.filters.registerButton1Callback(self.showDetailFilter)
|
||||
self.filters.registerButton2Name("_Refresh Stats")
|
||||
self.filters.registerButton2Callback(self.refreshStats)
|
||||
|
||||
# ToDo: store in config
|
||||
# ToDo: create popup to adjust column config
|
||||
# columns to display, keys match column name returned by sql, values in tuple are:
|
||||
# is column displayed, column heading, xalignment, formatting, celltype
|
||||
self.columns = [ ["siteName", True, "Site", 0.0, "%s", "str"]
|
||||
#,["tourney", False, "Tourney", 0.0, "%s", "str"] # true not allowed for this line
|
||||
, ["category", True, "Cat.", 0.0, "%s", "str"]
|
||||
, ["limitType", True, "Limit", 0.0, "%s", "str"]
|
||||
, ["currency", True, "Curr.", 0.0, "%s", "str"]
|
||||
, ["buyIn", True, "BuyIn", 1.0, "%3.2f", "str"]
|
||||
, ["fee", True, "Fee", 1.0, "%3.2f", "str"]
|
||||
, ["playerName", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code)
|
||||
, ["tourneyCount", True, "#", 1.0, "%1.0f", "str"]
|
||||
, ["itm", True, "ITM%", 1.0, "%3.2f", "str"]
|
||||
, ["1st", False, "1st", 1.0, "%1.0f", "str"]
|
||||
, ["2nd", True, "2nd", 1.0, "%1.0f", "str"]
|
||||
, ["3rd", True, "3rd", 1.0, "%1.0f", "str"]
|
||||
, ["unknownRank", True, "Rank?", 1.0, "%1.0f", "str"]
|
||||
, ["spent", True, "Spent", 1.0, "%3.2f", "str"]
|
||||
, ["won", True, "Won", 1.0, "%3.2f", "str"]
|
||||
, ["roi", True, "ROI%", 1.0, "%3.0f", "str"]
|
||||
, ["profitPerTourney", True,"$/Tour", 1.0, "%3.2f", "str"]]
|
||||
|
||||
self.stats_frame = gtk.Frame()
|
||||
self.stats_frame.show()
|
||||
|
||||
self.stats_vbox = gtk.VPaned()
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
# self.fillStatsFrame(self.stats_vbox)
|
||||
|
||||
#self.main_hbox.pack_start(self.filters.get_vbox())
|
||||
#self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True)
|
||||
self.main_hbox.pack1(self.filters.get_vbox())
|
||||
self.main_hbox.pack2(self.stats_frame)
|
||||
self.main_hbox.show()
|
||||
#end def __init__
|
||||
|
||||
def addGrid(self, vbox, query, numTourneys, tourneyTypes, playerids, sitenos, seats, dates):
|
||||
#print "start of addGrid query", query
|
||||
#print "start of addGrid. numTourneys:",numTourneys,"tourneyTypes:", tourneyTypes, "playerids:",playerids
|
||||
counter = 0
|
||||
row = 0
|
||||
sqlrow = 0
|
||||
grid=numTourneys #TODO: should this be numTourneyTypes?
|
||||
|
||||
query = self.sql.query[query]
|
||||
query = self.refineQuery(query, numTourneys, tourneyTypes, playerids, sitenos, seats, dates)
|
||||
self.cursor.execute(query)
|
||||
result = self.cursor.fetchall()
|
||||
#print "result of the big query in addGrid:",result
|
||||
colnames = [desc[0] for desc in self.cursor.description]
|
||||
|
||||
# pre-fetch some constant values:
|
||||
#self.cols_to_show = [x for x in self.columns if x[colshow]]
|
||||
#htourneytypeid_idx = colnames.index('tourneyTypeId')
|
||||
self.cols_to_show = self.columns #TODO do i need above 2 lines?
|
||||
|
||||
assert len(self.liststore) == grid, "len(self.liststore)="+str(len(self.liststore))+" grid-1="+str(grid)
|
||||
self.liststore.append( gtk.ListStore(*([str] * len(self.cols_to_show))) )
|
||||
view = gtk.TreeView(model=self.liststore[grid])
|
||||
view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
|
||||
#vbox.pack_start(view, expand=False, padding=3)
|
||||
vbox.add(view)
|
||||
textcell = gtk.CellRendererText()
|
||||
textcell50 = gtk.CellRendererText()
|
||||
textcell50.set_property('xalign', 0.5)
|
||||
numcell = gtk.CellRendererText()
|
||||
numcell.set_property('xalign', 1.0)
|
||||
assert len(self.listcols) == grid
|
||||
self.listcols.append( [] )
|
||||
|
||||
# Create header row eg column: ("game", True, "Game", 0.0, "%s")
|
||||
for col, column in enumerate(self.cols_to_show):
|
||||
if column[colalias] == 'game' and holecards:
|
||||
s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading]
|
||||
else:
|
||||
s = column[colheading]
|
||||
self.listcols[grid].append(gtk.TreeViewColumn(s))
|
||||
view.append_column(self.listcols[grid][col])
|
||||
if column[colformat] == '%s':
|
||||
if column[colxalign] == 0.0:
|
||||
self.listcols[grid][col].pack_start(textcell, expand=True)
|
||||
self.listcols[grid][col].add_attribute(textcell, 'text', col)
|
||||
cellrend = textcell
|
||||
else:
|
||||
self.listcols[grid][col].pack_start(textcell50, expand=True)
|
||||
self.listcols[grid][col].add_attribute(textcell50, 'text', col)
|
||||
cellrend = textcell50
|
||||
self.listcols[grid][col].set_expand(True)
|
||||
else:
|
||||
self.listcols[grid][col].pack_start(numcell, expand=True)
|
||||
self.listcols[grid][col].add_attribute(numcell, 'text', col)
|
||||
self.listcols[grid][col].set_expand(True)
|
||||
cellrend = numcell
|
||||
#self.listcols[grid][col].set_alignment(column[colxalign]) # no effect?
|
||||
self.listcols[grid][col].set_clickable(True)
|
||||
self.listcols[grid][col].connect("clicked", self.sortCols, (col,grid))
|
||||
if col == 0:
|
||||
self.listcols[grid][col].set_sort_order(gtk.SORT_DESCENDING)
|
||||
self.listcols[grid][col].set_sort_indicator(True)
|
||||
if column[coltype] == 'cash':
|
||||
self.listcols[grid][col].set_cell_data_func(numcell, self.ledger_style_render_func)
|
||||
else:
|
||||
self.listcols[grid][col].set_cell_data_func(cellrend, self.reset_style_render_func)
|
||||
|
||||
rows = len(result) # +1 for title row
|
||||
|
||||
while sqlrow < rows:
|
||||
treerow = []
|
||||
for col,column in enumerate(self.cols_to_show):
|
||||
if column[colalias] in colnames:
|
||||
value = result[sqlrow][colnames.index(column[colalias])]
|
||||
else:
|
||||
value = 111
|
||||
if value and value != -999:
|
||||
treerow.append(column[colformat] % value)
|
||||
else:
|
||||
treerow.append(' ')
|
||||
#print "addGrid, just before end of big for. grid:",grid,"treerow:",treerow
|
||||
iter = self.liststore[grid].append(treerow)
|
||||
sqlrow += 1
|
||||
row += 1
|
||||
vbox.show_all()
|
||||
#end def addGrid
|
||||
|
||||
def createStatsTable(self, vbox, tourneyTypes, playerids, sitenos, seats, dates):
|
||||
startTime = time()
|
||||
show_detail = True
|
||||
|
||||
# Scrolled window for summary table
|
||||
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
||||
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
swin.show()
|
||||
vbox.pack1(swin)
|
||||
|
||||
numTourneys = self.filters.getNumTourneys()
|
||||
self.addGrid(swin, 'tourneyPlayerDetailedStats', numTourneys, tourneyTypes, playerids
|
||||
,sitenos, seats, dates)
|
||||
|
||||
#if 'allplayers' in groups and groups['allplayers']:
|
||||
# can't currently do this combination so skip detailed table
|
||||
show_detail = False
|
||||
|
||||
if show_detail:
|
||||
# Separator
|
||||
vbox2 = gtk.VBox(False, 0)
|
||||
heading = gtk.Label(self.filterText['handhead'])
|
||||
heading.show()
|
||||
vbox2.pack_start(heading, expand=False, padding=3)
|
||||
|
||||
# Scrolled window for detailed table (display by hand)
|
||||
swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
|
||||
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
swin.show()
|
||||
vbox2.pack_start(swin, expand=True, padding=3)
|
||||
vbox.pack2(vbox2)
|
||||
vbox2.show()
|
||||
|
||||
# Detailed table
|
||||
flags[0] = True
|
||||
flags[2] = 1
|
||||
self.addGrid(swin, 'playerDetailedStats', flags, playerids, sitenos, seats, dates)
|
||||
|
||||
self.db.rollback()
|
||||
print "Stats page displayed in %4.2f seconds" % (time() - startTime)
|
||||
#end def createStatsTable
|
||||
|
||||
def fillStatsFrame(self, vbox):
|
||||
tourneyTypes = self.filters.getTourneyTypes()
|
||||
#tourneys = self.tourneys.getTourneys()
|
||||
sites = self.filters.getSites()
|
||||
heroes = self.filters.getHeroes()
|
||||
siteids = self.filters.getSiteIds()
|
||||
seats = self.filters.getSeats()
|
||||
dates = self.filters.getDates()
|
||||
sitenos = []
|
||||
playerids = []
|
||||
|
||||
# Which sites are selected?
|
||||
for site in sites:
|
||||
if sites[site] == True:
|
||||
sitenos.append(siteids[site])
|
||||
_hname = Charset.to_utf8(heroes[site])
|
||||
result = self.db.get_player_id(self.conf, site, _hname)
|
||||
if result is not None:
|
||||
playerids.append(int(result))
|
||||
|
||||
if not sitenos:
|
||||
#Should probably pop up here.
|
||||
print "No sites selected - defaulting to PokerStars"
|
||||
sitenos = [2]
|
||||
if not playerids:
|
||||
print "No player ids found"
|
||||
return
|
||||
|
||||
self.createStatsTable(vbox, tourneyTypes, playerids, sitenos, seats, dates)
|
||||
#end def fillStatsFrame
|
||||
|
||||
def get_vbox(self):
|
||||
"""returns the vbox of this thread"""
|
||||
return self.main_hbox
|
||||
#end def get_vbox
|
||||
|
||||
def refineQuery(self, query, numTourneys, tourneyTypes, playerids, sitenos, seats, dates):
|
||||
having = ''
|
||||
|
||||
#print "start of refinequery, playerids:",playerids
|
||||
if playerids:
|
||||
nametest = str(tuple(playerids))
|
||||
nametest = nametest.replace("L", "")
|
||||
nametest = nametest.replace(",)",")")
|
||||
else:
|
||||
nametest = "1 = 2"
|
||||
#print "refinequery, nametest after initial creation:",nametest
|
||||
pname = "p.name"
|
||||
# set flag in self.columns to not show player name column
|
||||
#[x for x in self.columns if x[0] == 'pname'][0][1] = False #TODO: fix and reactivate
|
||||
|
||||
query = query.replace("<nametest>", nametest)
|
||||
query = query.replace("<playerName>", pname)
|
||||
query = query.replace("<havingclause>", having)
|
||||
|
||||
#TODO: remove, or replace with tourneytest
|
||||
#gametest = ""
|
||||
#q = []
|
||||
#for m in self.filters.display.items():
|
||||
# if m[0] == 'Games' and m[1]:
|
||||
# for n in games:
|
||||
# if games[n]:
|
||||
# q.append(n)
|
||||
# if len(q) > 0:
|
||||
# gametest = str(tuple(q))
|
||||
# gametest = gametest.replace("L", "")
|
||||
# gametest = gametest.replace(",)",")")
|
||||
# gametest = gametest.replace("u'","'")
|
||||
# gametest = "and gt.category in %s" % gametest
|
||||
# else:
|
||||
# gametest = "and gt.category IS NULL"
|
||||
#query = query.replace("<game_test>", gametest)
|
||||
|
||||
sitetest = ""
|
||||
q = []
|
||||
for m in self.filters.display.items():
|
||||
if m[0] == 'Sites' and m[1]:
|
||||
for n in sitenos:
|
||||
q.append(n)
|
||||
if len(q) > 0:
|
||||
sitetest = str(tuple(q))
|
||||
sitetest = sitetest.replace("L", "")
|
||||
sitetest = sitetest.replace(",)",")")
|
||||
sitetest = sitetest.replace("u'","'")
|
||||
sitetest = "and tt.siteId in %s" % sitetest#[1:-1]
|
||||
else:
|
||||
sitetest = "and tt.siteId IS NULL"
|
||||
#print "refinequery, sitetest before its use for replacement:",sitetest
|
||||
query = query.replace("<sitetest>", sitetest)
|
||||
|
||||
if seats:
|
||||
query = query.replace('<seats_test>', 'between ' + str(seats['from']) + ' and ' + str(seats['to']))
|
||||
if 'show' in seats and seats['show']:
|
||||
query = query.replace('<groupbyseats>', ',h.seats')
|
||||
query = query.replace('<orderbyseats>', ',h.seats')
|
||||
else:
|
||||
query = query.replace('<groupbyseats>', '')
|
||||
query = query.replace('<orderbyseats>', '')
|
||||
else:
|
||||
query = query.replace('<seats_test>', 'between 0 and 100')
|
||||
query = query.replace('<groupbyseats>', '')
|
||||
query = query.replace('<orderbyseats>', '')
|
||||
|
||||
#lims = [int(x) for x in limits if x.isdigit()]
|
||||
#potlims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'pl']
|
||||
#nolims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'nl']
|
||||
#bbtest = "and ( (gt.limitType = 'fl' and gt.bigBlind in "
|
||||
# and ( (limit and bb in()) or (nolimit and bb in ()) )
|
||||
#if lims:
|
||||
# blindtest = str(tuple(lims))
|
||||
# blindtest = blindtest.replace("L", "")
|
||||
# blindtest = blindtest.replace(",)",")")
|
||||
# bbtest = bbtest + blindtest + ' ) '
|
||||
#else:
|
||||
# bbtest = bbtest + '(-1) ) '
|
||||
#bbtest = bbtest + " or (gt.limitType = 'pl' and gt.bigBlind in "
|
||||
#if potlims:
|
||||
# blindtest = str(tuple(potlims))
|
||||
# blindtest = blindtest.replace("L", "")
|
||||
# blindtest = blindtest.replace(",)",")")
|
||||
# bbtest = bbtest + blindtest + ' ) '
|
||||
#else:
|
||||
# bbtest = bbtest + '(-1) ) '
|
||||
#bbtest = bbtest + " or (gt.limitType = 'nl' and gt.bigBlind in "
|
||||
#if nolims:
|
||||
# blindtest = str(tuple(nolims))
|
||||
# blindtest = blindtest.replace("L", "")
|
||||
# blindtest = blindtest.replace(",)",")")
|
||||
# bbtest = bbtest + blindtest + ' ) )'
|
||||
#else:
|
||||
# bbtest = bbtest + '(-1) ) )'
|
||||
|
||||
#if type == 'ring':
|
||||
# bbtest = bbtest + " and gt.type = 'ring' "
|
||||
#elif type == 'tour':
|
||||
#bbtest = " and gt.type = 'tour' "
|
||||
|
||||
#query = query.replace("<gtbigBlind_test>", bbtest)
|
||||
|
||||
#query = query.replace("<orderbyhgameTypeId>", "")
|
||||
|
||||
# process self.detailFilters (a list of tuples)
|
||||
flagtest = ''
|
||||
#self.detailFilters = [('h.seats', 5, 6)] # for debug
|
||||
if self.detailFilters:
|
||||
for f in self.detailFilters:
|
||||
if len(f) == 3:
|
||||
# X between Y and Z
|
||||
flagtest += ' and %s between %s and %s ' % (f[0], str(f[1]), str(f[2]))
|
||||
query = query.replace("<flagtest>", flagtest)
|
||||
|
||||
# allow for differences in sql cast() function:
|
||||
if self.db.backend == self.db.MYSQL_INNODB:
|
||||
query = query.replace("<signed>", 'signed ')
|
||||
else:
|
||||
query = query.replace("<signed>", '')
|
||||
|
||||
# Filter on dates
|
||||
query = query.replace("<datestest>", " between '" + dates[0] + "' and '" + dates[1] + "'")
|
||||
|
||||
# Group by position?
|
||||
#if groups['posn']:
|
||||
# #query = query.replace("<position>", "case hp.position when '0' then 'Btn' else hp.position end")
|
||||
# query = query.replace("<position>", "hp.position")
|
||||
# # set flag in self.columns to show posn column
|
||||
# [x for x in self.columns if x[0] == 'plposition'][0][1] = True
|
||||
#else:
|
||||
# query = query.replace("<position>", "gt.base")
|
||||
# # unset flag in self.columns to hide posn column
|
||||
# [x for x in self.columns if x[0] == 'plposition'][0][1] = False
|
||||
|
||||
#print "query at end of refine query:", query
|
||||
return(query)
|
||||
#end def refineQuery
|
||||
|
||||
def refreshStats(self, widget, data):
|
||||
self.last_pos = self.stats_vbox.get_position()
|
||||
try: self.stats_vbox.destroy()
|
||||
except AttributeError: pass
|
||||
self.liststore = []
|
||||
self.listcols = []
|
||||
#self.stats_vbox = gtk.VBox(False, 0)
|
||||
self.stats_vbox = gtk.VPaned()
|
||||
self.stats_vbox.show()
|
||||
self.stats_frame.add(self.stats_vbox)
|
||||
self.fillStatsFrame(self.stats_vbox)
|
||||
if self.last_pos > 0:
|
||||
self.stats_vbox.set_position(self.last_pos)
|
||||
#end def refreshStats
|
||||
|
||||
def reset_style_render_func(self, treeviewcolumn, cell, model, iter):
|
||||
cell.set_property('foreground', 'black')
|
||||
#end def reset_style_render_func
|
||||
|
||||
def sortCols(self, col, nums):
|
||||
try:
|
||||
#This doesn't actually work yet - clicking heading in top section sorts bottom section :-(
|
||||
(n, grid) = nums
|
||||
if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
|
||||
col.set_sort_order(gtk.SORT_DESCENDING)
|
||||
else:
|
||||
col.set_sort_order(gtk.SORT_ASCENDING)
|
||||
self.liststore[grid].set_sort_column_id(n, col.get_sort_order())
|
||||
self.liststore[grid].set_sort_func(n, self.sortnums, (n,grid))
|
||||
for i in xrange(len(self.listcols[grid])):
|
||||
self.listcols[grid][i].set_sort_indicator(False)
|
||||
self.listcols[grid][n].set_sort_indicator(True)
|
||||
# use this listcols[col].set_sort_indicator(True)
|
||||
# to turn indicator off for other cols
|
||||
except:
|
||||
err = traceback.extract_tb(sys.exc_info()[2])
|
||||
print "***sortCols error: " + str(sys.exc_info()[1])
|
||||
print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
|
||||
#end def sortCols
|
||||
#end class GuiTourneyPlayerStats
|
|
@ -644,5 +644,9 @@ Left-Drag to Move"
|
|||
<database db_ip="localhost" db_server="sqlite" db_name="fpdb.db3" db_user="fpdb" db_pass="fpdb"/>
|
||||
</supported_databases>
|
||||
|
||||
<email>
|
||||
<email siteName="PokerStars" fetchType="request-summary" host="YOUR_EMAIL_SERVER" username="YOUR_EMAIL_USERNAME" password="YOUR_EMAIL_PASSWORD" useSsl="True" folder="INBOX"/>
|
||||
</email>
|
||||
</FreePokerToolsConfig>
|
||||
|
||||
<!-- IMPORTANT: Please note that fpdb stores your email password in clear text.
|
||||
So do not post a config containing a password on the Internet or anywhere else without removing the password! -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2008-2010, Ray E. Barker
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Eric Blade
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
@ -38,7 +38,6 @@ from Exceptions import *
|
|||
import DerivedStats
|
||||
import Card
|
||||
|
||||
|
||||
class Hand(object):
|
||||
|
||||
###############################################################3
|
||||
|
@ -57,7 +56,7 @@ class Hand(object):
|
|||
self.siteId = self.SITEIDS[sitename]
|
||||
self.stats = DerivedStats.DerivedStats(self)
|
||||
self.gametype = gametype
|
||||
self.starttime = 0
|
||||
self.startTime = 0
|
||||
self.handText = handText
|
||||
self.handid = 0
|
||||
self.cancelled = False
|
||||
|
@ -69,24 +68,32 @@ class Hand(object):
|
|||
self.maxseats = None
|
||||
self.counted_seats = 0
|
||||
self.buttonpos = 0
|
||||
|
||||
#tourney stuff
|
||||
self.tourNo = None
|
||||
self.tourneyId = None
|
||||
self.tourneyTypeId = None
|
||||
self.buyin = None
|
||||
self.buyinCurrency = None
|
||||
self.buyInChips = None
|
||||
self.fee = None # the Database code is looking for this one .. ?
|
||||
self.level = None
|
||||
self.mixed = None
|
||||
# Some attributes for hand from a tourney
|
||||
self.speed = "Normal"
|
||||
self.isRebuy = False
|
||||
self.isAddOn = False
|
||||
self.isKO = False
|
||||
self.isHU = False
|
||||
self.isMatrix = False
|
||||
self.isShootout = False
|
||||
self.added = None
|
||||
self.addedCurrency = None
|
||||
self.tourneyComment = None
|
||||
|
||||
self.seating = []
|
||||
self.players = []
|
||||
self.posted = []
|
||||
|
||||
self.tourneysPlayersIds = []
|
||||
|
||||
# Collections indexed by street names
|
||||
self.bets = {}
|
||||
self.lastBet = {}
|
||||
|
@ -135,8 +142,6 @@ class Hand(object):
|
|||
("TABLE NAME", self.tablename),
|
||||
("HERO", self.hero),
|
||||
("MAXSEATS", self.maxseats),
|
||||
("TOURNAMENT NO", self.tourNo),
|
||||
("BUYIN", self.buyin),
|
||||
("LEVEL", self.level),
|
||||
("MIXED", self.mixed),
|
||||
("LASTBET", self.lastBet),
|
||||
|
@ -152,7 +157,20 @@ class Hand(object):
|
|||
("TOTAL POT", self.totalpot),
|
||||
("TOTAL COLLECTED", self.totalcollected),
|
||||
("RAKE", self.rake),
|
||||
("START TIME", self.starttime),
|
||||
("START TIME", self.startTime),
|
||||
("TOURNAMENT NO", self.tourNo),
|
||||
("TOURNEY ID", self.tourneyId),
|
||||
("TOURNEY TYPE ID", self.tourneyTypeId),
|
||||
("BUYIN", self.buyin),
|
||||
("BUYIN CURRENCY", self.buyinCurrency),
|
||||
("BUYIN CHIPS", self.buyInChips),
|
||||
("FEE", self.fee),
|
||||
("IS REBUY", self.isRebuy),
|
||||
("IS ADDON", self.isAddOn),
|
||||
("IS KO", self.isKO),
|
||||
("IS MATRIX", self.isMatrix),
|
||||
("IS SHOOTOUT", self.isShootout),
|
||||
("TOURNEY COMMENT", self.tourneyComment),
|
||||
)
|
||||
|
||||
structs = ( ("PLAYERS", self.players),
|
||||
|
@ -167,6 +185,7 @@ class Hand(object):
|
|||
("BOARD", self.board),
|
||||
("DISCARDS", self.discards),
|
||||
("HOLECARDS", self.holecards),
|
||||
("TOURNEYS PLAYER IDS", self.tourneysPlayersIds),
|
||||
)
|
||||
str = ''
|
||||
for (name, var) in vars:
|
||||
|
@ -208,6 +227,15 @@ dealt whether they were seen in a 'dealt to' line
|
|||
|
||||
#Gametypes
|
||||
self.dbid_gt = db.getGameTypeId(self.siteId, self.gametype)
|
||||
|
||||
if self.tourNo!=None:
|
||||
self.tourneyTypeId = db.createTourneyType(self)
|
||||
db.commit()
|
||||
self.tourneyId = db.createOrUpdateTourney(self, "HHC")
|
||||
db.commit()
|
||||
self.tourneysPlayersIds = db.createOrUpdateTourneysPlayers(self, "HHC")
|
||||
db.commit()
|
||||
#end def prepInsert
|
||||
|
||||
def insert(self, db):
|
||||
""" Function to insert Hand into database
|
||||
|
@ -230,17 +258,15 @@ db: a connected Database object"""
|
|||
|
||||
self.dbid_hands = db.storeHand(hh)
|
||||
db.storeHandsPlayers(self.dbid_hands, self.dbid_pids, self.stats.getHandsPlayers())
|
||||
# HandsActions - all actions for all players for all streets - self.actions
|
||||
# TODO HandsActions - all actions for all players for all streets - self.actions
|
||||
# HudCache data can be generated from HandsActions (HandsPlayers?)
|
||||
# Tourneys ?
|
||||
# TourneysPlayers
|
||||
else:
|
||||
log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo'])
|
||||
self.is_duplicate = True # i.e. don't update hudcache
|
||||
raise FpdbHandDuplicate(hh['siteHandNo'])
|
||||
|
||||
def updateHudCache(self, db):
|
||||
db.storeHudCache(self.dbid_gt, self.dbid_pids, self.starttime, self.stats.getHandsPlayers())
|
||||
db.storeHudCache(self.dbid_gt, self.dbid_pids, self.startTime, self.stats.getHandsPlayers())
|
||||
|
||||
def select(self, handId):
|
||||
""" Function to create Hand object from database """
|
||||
|
@ -603,10 +629,10 @@ Map the tuple self.gametype onto the pokerstars string describing it
|
|||
gs = gs + " %s (%s) - " % (self.getGameTypeAsString(), self.getStakesAsString())
|
||||
|
||||
try:
|
||||
timestr = datetime.datetime.strftime(self.starttime, '%Y/%m/%d %H:%M:%S ET')
|
||||
timestr = datetime.datetime.strftime(self.startTime, '%Y/%m/%d %H:%M:%S ET')
|
||||
except TypeError:
|
||||
print "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:", self.starttime
|
||||
print "*** Make sure your HandHistoryConverter is setting hand.starttime properly!"
|
||||
print "*** ERROR - HAND: calling writeGameLine with unexpected STARTTIME value, expecting datetime.date object, received:", self.startTime
|
||||
print "*** Make sure your HandHistoryConverter is setting hand.startTime properly!"
|
||||
print "*** Game String:", gs
|
||||
return gs
|
||||
else:
|
||||
|
@ -806,7 +832,7 @@ class HoldemOmahaHand(Hand):
|
|||
T.h1[
|
||||
T.span(class_='site')["%s Game #%s]" % ('PokerStars', self.handid)],
|
||||
T.span(class_='type_limit')[ "%s ($%s/$%s)" %(self.getGameTypeAsString(), self.sb, self.bb) ],
|
||||
T.span(class_='date')[ datetime.datetime.strftime(self.starttime,'%Y/%m/%d - %H:%M:%S ET') ]
|
||||
T.span(class_='date')[ datetime.datetime.strftime(self.startTime,'%Y/%m/%d - %H:%M:%S ET') ]
|
||||
],
|
||||
T.h2[ "Table '%s' %d-max Seat #%s is the button" %(self.tablename,
|
||||
self.maxseats, self.buttonpos)],
|
||||
|
@ -1556,7 +1582,7 @@ limit 1""", {'handid':handid})
|
|||
SELECT
|
||||
h.sitehandno as hid,
|
||||
h.tablename as table,
|
||||
h.handstart as starttime
|
||||
h.startTime as startTime
|
||||
FROM
|
||||
hands as h
|
||||
WHERE h.id = %(handid)s
|
||||
|
@ -1564,7 +1590,7 @@ WHERE h.id = %(handid)s
|
|||
res = c.fetchone()
|
||||
h.handid = res[0]
|
||||
h.tablename = res[1]
|
||||
h.starttime = res[2] # automatically a datetime
|
||||
h.startTime = res[2] # automatically a datetime
|
||||
|
||||
# PlayerStacks
|
||||
c.execute("""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""HandHistory.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
@ -35,7 +35,6 @@ log = logging.getLogger("parser")
|
|||
|
||||
|
||||
import Hand
|
||||
import Tourney
|
||||
from Exceptions import FpdbParseError
|
||||
import Configuration
|
||||
|
||||
|
@ -495,6 +494,23 @@ or None if we fail to get the info """
|
|||
|
||||
def getTourney(self):
|
||||
return self.tourney
|
||||
|
||||
@staticmethod
|
||||
def changeTimezone(time, givenTimezone, wantedTimezone):
|
||||
if givenTimezone=="ET" and wantedTimezone=="UTC":
|
||||
# approximate rules for ET daylight savings time:
|
||||
if ( time.month == 12 # all of Dec
|
||||
or (time.month == 11 and time.day > 4) # and most of November
|
||||
or time.month < 3 # and all of Jan/Feb
|
||||
or (time.month == 3 and time.day < 11) ): # and 1st 10 days of March
|
||||
offset = datetime.timedelta(hours=5) # are EST: assume 5 hour offset (ET without daylight saving)
|
||||
else:
|
||||
offset = datetime.timedelta(hours=4) # rest is EDT: assume 4 hour offset (ET with daylight saving)
|
||||
# adjust time into UTC:
|
||||
time = time + offset
|
||||
#print " tz = %s start = %s" % (tz, str(hand.starttime))
|
||||
return time
|
||||
#end @staticmethod def changeTimezone
|
||||
|
||||
@staticmethod
|
||||
def getTableTitleRe(type, table_name=None, tournament = None, table_number=None):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Hello.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Hud.py
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import imaplib
|
||||
import Configuration
|
||||
import os
|
||||
import pprint
|
||||
|
||||
pp = pprint.PrettyPrinter(indent=4)
|
||||
|
||||
def open_imap_connection(verbose=False):
|
||||
# Read the config file
|
||||
# FIXME
|
||||
hostname = 'imap.gmail.com'
|
||||
port = 993
|
||||
username = 'slartibartfast'
|
||||
password = '42'
|
||||
|
||||
# Connect
|
||||
if verbose: print "Connecting to %s" % hostname
|
||||
connection = imaplib.IMAP4_SSL(hostname)
|
||||
|
||||
# Login to our account
|
||||
if verbose: print "Logging in as %s" % username
|
||||
connection.login(username, password)
|
||||
return connection
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Read the config file
|
||||
# FIXME
|
||||
folder = "INBOX"
|
||||
c = open_imap_connection(verbose=True)
|
||||
|
||||
try:
|
||||
typ, data = c.list(directory=folder)
|
||||
print typ, data
|
||||
|
||||
c.select('INBOX', readonly=True)
|
||||
|
||||
typ, msg_ids = c.search(None, '(SUBJECT "Results for PokerStars Tournament *")')
|
||||
print typ, msg_ids
|
||||
msgidlist = msg_ids[0].split(' ')
|
||||
print msgidlist
|
||||
|
||||
for msg in msgidlist:
|
||||
print 'HEADER:'
|
||||
typ, msg_data = c.fetch(msg, '(BODY.PEEK[HEADER])')
|
||||
for response_part in msg_data:
|
||||
if isinstance(response_part, tuple):
|
||||
print response_part[1]
|
||||
|
||||
print 'BODY TEXT:'
|
||||
typ, msg_data = c.fetch(msg, '(BODY.PEEK[TEXT])')
|
||||
for response_part in msg_data:
|
||||
if isinstance(response_part, tuple):
|
||||
print response_part[1]
|
||||
|
||||
|
||||
finally:
|
||||
try:
|
||||
c.close()
|
||||
except:
|
||||
pass
|
||||
c.logout()
|
81
pyfpdb/ImapFetcher.py
Executable file
81
pyfpdb/ImapFetcher.py
Executable file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
"""This file is to fetch summaries through IMAP and pass them on to the appropriate parser"""
|
||||
#see http://docs.python.org/library/imaplib.html for the python interface
|
||||
#see http://tools.ietf.org/html/rfc2060#section-6.4.4 for IMAP4 search criteria
|
||||
|
||||
from imaplib import IMAP4, IMAP4_SSL
|
||||
import PokerStarsSummary
|
||||
|
||||
def splitPokerStarsSummaries(emailText):
|
||||
splitSummaries=emailText.split("\nPokerStars Tournament #")[1:]
|
||||
for i in range(len(splitSummaries)):
|
||||
splitSummaries[i]="PokerStars Tournament #"+splitSummaries[i]
|
||||
return splitSummaries
|
||||
#end def emailText
|
||||
|
||||
def run(config, db):
|
||||
#print "start of IS.run"
|
||||
server=None
|
||||
#try:
|
||||
#print "useSSL",config.email.useSsl,"host",config.email.host
|
||||
if config.email.useSsl:
|
||||
server = IMAP4_SSL(config.email.host)
|
||||
else:
|
||||
server = IMAP4(config.email.host)
|
||||
response = server.login(config.email.username, config.email.password) #TODO catch authentication error
|
||||
print "response to logging in:",response
|
||||
#print "server.list():",server.list() #prints list of folders
|
||||
|
||||
response = server.select(config.email.folder)
|
||||
#print "response to selecting INBOX:",response
|
||||
if response[0]!="OK":
|
||||
raise error #TODO: show error message
|
||||
|
||||
neededMessages=[]
|
||||
response, searchData = server.search(None, "SUBJECT", "PokerStars Tournament History Request")
|
||||
for messageNumber in searchData[0].split(" "):
|
||||
response, headerData = server.fetch(messageNumber, "(BODY[HEADER.FIELDS (SUBJECT)])")
|
||||
#print "response to fetch subject:",response
|
||||
if response!="OK":
|
||||
raise error #TODO: show error message
|
||||
if headerData[1].find("Subject: PokerStars Tournament History Request - Last x")!=1:
|
||||
neededMessages.append(("PS", messageNumber))
|
||||
|
||||
if (len(neededMessages)==0):
|
||||
raise error #TODO: show error message
|
||||
for messageData in neededMessages:
|
||||
response, bodyData = server.fetch(messageData[1], "(UID BODY[TEXT])")
|
||||
bodyData=bodyData[0][1]
|
||||
if response!="OK":
|
||||
raise error #TODO: show error message
|
||||
if messageData[0]=="PS":
|
||||
summaryTexts=(splitPokerStarsSummaries(bodyData))
|
||||
for summaryText in summaryTexts:
|
||||
result=PokerStarsSummary.PokerStarsSummary(db=db, config=config, siteName=u"PokerStars", summaryText=summaryText, builtFrom = "IMAP")
|
||||
#print "finished importing a PS summary with result:",result
|
||||
#TODO: count results and output to shell like hand importer does
|
||||
|
||||
print "completed running Imap import, closing server connection"
|
||||
#finally:
|
||||
# try:
|
||||
server.close()
|
||||
# finally:
|
||||
# pass
|
||||
server.logout()
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Mucked.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -138,7 +138,7 @@ class OnGame(HandHistoryConverter):
|
|||
#TODO: Do conversion from GMT to ET
|
||||
#TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this)
|
||||
|
||||
hand.starttime = time.strptime(m.group('DATETIME'), "%d %b %Y %I:%M %p")
|
||||
hand.startTime = time.strptime(m.group('DATETIME'), "%d %b %Y %I:%M %p")
|
||||
#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')))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Ray E. Barker
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2009-2010, Grigorij Indigirkin
|
||||
|
@ -281,7 +281,7 @@ class PartyPoker(HandHistoryConverter):
|
|||
'July','August','September','October','November','December']
|
||||
month = months.index(m2.group('M')) + 1
|
||||
datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
|
||||
hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
|
||||
hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
|
||||
# FIXME: some timezone correction required
|
||||
#tzShift = defaultdict(lambda:0, {'EDT': -5, 'EST': -6, 'MSKS': 3})
|
||||
#hand.starttime -= datetime.timedelta(hours=tzShift[m2.group('TZ')])
|
||||
|
|
156
pyfpdb/PokerStarsSummary.py
Normal file
156
pyfpdb/PokerStarsSummary.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
"""pokerstars-specific summary parsing code"""
|
||||
|
||||
from decimal import Decimal
|
||||
import datetime
|
||||
|
||||
from Exceptions import FpdbParseError
|
||||
from HandHistoryConverter import *
|
||||
import PokerStarsToFpdb
|
||||
from TourneySummary import *
|
||||
|
||||
class PokerStarsSummary(TourneySummary):
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl', 'LIMIT':'fl' }
|
||||
games = { # base, category
|
||||
"Hold'em" : ('hold','holdem'),
|
||||
'Omaha' : ('hold','omahahi'),
|
||||
'Omaha Hi/Lo' : ('hold','omahahilo'),
|
||||
'Razz' : ('stud','razz'),
|
||||
'RAZZ' : ('stud','razz'),
|
||||
'7 Card Stud' : ('stud','studhi'),
|
||||
'7 Card Stud Hi/Lo' : ('stud','studhilo'),
|
||||
'Badugi' : ('draw','badugi'),
|
||||
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
|
||||
'5 Card Draw' : ('draw','fivedraw')
|
||||
}
|
||||
|
||||
re_TourNo = re.compile("\#[0-9]+,")
|
||||
re_Entries = re.compile("[0-9]+")
|
||||
re_Prizepool = re.compile("\$[0-9]+\.[0-9]+")
|
||||
re_Player = re.compile(u"""(?P<RANK>[0-9]+):\s(?P<NAME>.*)\s\(.*\),(\s)?(\$(?P<WINNINGS>[0-9]+\.[0-9]+))?(?P<STILLPLAYING>still\splaying)?""")
|
||||
re_BuyInFee = re.compile("(?P<BUYIN>[0-9]+\.[0-9]+).*(?P<FEE>[0-9]+\.[0-9]+)")
|
||||
re_FPP = re.compile("(?P<FPP>[0-9]+)\sFPP")
|
||||
#note: the dollar and cent in the below line are currency-agnostic
|
||||
re_Added = re.compile("(?P<DOLLAR>[0-9]+)\.(?P<CENT>[0-9]+)\s(?P<CURRENCY>[A-Z]+)(\sadded\sto\sthe\sprize\spool\sby\sPokerStars)")
|
||||
re_DateTime = re.compile("\[(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)")
|
||||
re_DateTimeET = re.compile("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)")
|
||||
re_GameInfo = re.compile(u""".+(?P<LIMIT>No\sLimit|Limit|LIMIT|Pot\sLimit)\s(?P<GAME>Hold\'em|Razz|RAZZ|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)""")
|
||||
|
||||
def parseSummary(self):
|
||||
lines=self.summaryText.splitlines()
|
||||
|
||||
self.tourNo = self.re_TourNo.findall(lines[0])[0][1:-1] #ignore game and limit type as thats not recorded
|
||||
|
||||
result=self.re_GameInfo.search(lines[0])
|
||||
result=result.groupdict()
|
||||
self.gametype['limitType']=self.limits[result['LIMIT']]
|
||||
self.gametype['category']=self.games[result['GAME']][1]
|
||||
|
||||
if lines[1].find("$")!=-1: #TODO: move this into a method and call that from PokerStarsToFpdb.py:269 if hand.buyinCurrency=="USD" etc.
|
||||
self.currency="USD"
|
||||
elif lines[1].find(u"€")!=-1:
|
||||
self.currency="EUR"
|
||||
elif lines[1].find("FPP")!=-1:
|
||||
self.currency="PSFP"
|
||||
else:
|
||||
raise FpdbParseError("didn't recognise buyin currency in:"+lines[1])
|
||||
|
||||
if self.currency=="USD" or self.currency=="EUR":
|
||||
result=self.re_BuyInFee.search(lines[1])
|
||||
result=result.groupdict()
|
||||
self.buyin=int(100*Decimal(result['BUYIN']))
|
||||
self.fee=int(100*Decimal(result['FEE']))
|
||||
elif self.currency=="PSFP":
|
||||
result=self.re_FPP.search(lines[1])
|
||||
result=result.groupdict()
|
||||
self.buyin=int(Decimal(result['FPP']))
|
||||
self.fee=0
|
||||
|
||||
currentLine=2
|
||||
self.entries = self.re_Entries.findall(lines[currentLine])[0]
|
||||
currentLine+=1 #note that I chose to make the code keep state (the current line number)
|
||||
#as that means it'll fail rather than silently skip potentially valuable information
|
||||
#print "after entries lines[currentLine]", lines[currentLine]
|
||||
|
||||
result=self.re_Added.search(lines[currentLine])
|
||||
if result:
|
||||
result=result.groupdict()
|
||||
self.added=100*int(Decimal(result['DOLLAR']))+int(Decimal(result['CENT']))
|
||||
self.addedCurrency=result['CURRENCY']
|
||||
currentLine+=1
|
||||
else:
|
||||
self.added=0
|
||||
self.addedCurrency="NA"
|
||||
#print "after added/entries lines[currentLine]", lines[currentLine]
|
||||
|
||||
result=self.re_Prizepool.findall(lines[currentLine])
|
||||
if result:
|
||||
self.prizepool = result[0]
|
||||
self.prizepool = self.prizepool[1:-3]+self.prizepool[-2:]
|
||||
currentLine+=1
|
||||
#print "after prizepool lines[currentLine]", lines[currentLine]
|
||||
|
||||
useET=False
|
||||
result=self.re_DateTime.search(lines[currentLine])
|
||||
if not result:
|
||||
print "in not result starttime"
|
||||
useET=True
|
||||
result=self.re_DateTimeET.search(lines[currentLine])
|
||||
result=result.groupdict()
|
||||
datetimestr = "%s/%s/%s %s:%s:%s" % (result['Y'], result['M'],result['D'],result['H'],result['MIN'],result['S'])
|
||||
self.startTime= datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
|
||||
self.startTime = HandHistoryConverter.changeTimezone(self.startTime, "ET", "UTC")
|
||||
currentLine+=1
|
||||
|
||||
if useET:
|
||||
result=self.re_DateTimeET.search(lines[currentLine])
|
||||
else:
|
||||
result=self.re_DateTime.search(lines[currentLine])
|
||||
if result:
|
||||
result=result.groupdict()
|
||||
datetimestr = "%s/%s/%s %s:%s:%s" % (result['Y'], result['M'],result['D'],result['H'],result['MIN'],result['S'])
|
||||
self.endTime= datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
|
||||
self.endTime = HandHistoryConverter.changeTimezone(self.endTime, "ET", "UTC")
|
||||
currentLine+=1
|
||||
|
||||
if lines[currentLine].find("Tournament is still in progress")!=-1:
|
||||
currentLine+=1
|
||||
|
||||
for i in range(currentLine,len(lines)-2): #lines with rank and winnings info
|
||||
if lines[i].find(":")==-1:
|
||||
break
|
||||
result=self.re_Player.search(lines[i])
|
||||
result=result.groupdict()
|
||||
rank=result['RANK']
|
||||
name=result['NAME']
|
||||
winnings=result['WINNINGS']
|
||||
|
||||
if winnings:
|
||||
winnings=int(100*Decimal(winnings))
|
||||
else:
|
||||
winnings=0
|
||||
|
||||
if result['STILLPLAYING']:
|
||||
#print "stillplaying"
|
||||
rank=None
|
||||
winnings=None
|
||||
|
||||
self.addPlayer(rank, name, winnings, self.currency, None, None, None)#TODO: currency, ko/addon/rebuy count -> need examples!
|
||||
#end def parseSummary
|
||||
#end class PokerStarsSummary
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -22,6 +22,7 @@
|
|||
|
||||
import sys
|
||||
from HandHistoryConverter import *
|
||||
from decimal import Decimal
|
||||
|
||||
# PokerStars HH Format
|
||||
|
||||
|
@ -40,6 +41,30 @@ class PokerStars(HandHistoryConverter):
|
|||
'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
|
||||
'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8)
|
||||
}
|
||||
|
||||
# translations from captured groups to fpdb info strings
|
||||
Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'),
|
||||
'0.40': ('0.10', '0.20'), '0.50': ('0.10', '0.25'), '1.00': ('0.25', '0.50'),
|
||||
'2.00': ('0.50', '1.00'), '2': ('0.50', '1.00'), '4' : ('1.00', '2.00'),
|
||||
'4.00': ('1.00', '2.00'), '6': ('1.00', '3.00'), '6.00': ('1.00', '3.00'),
|
||||
'10.00': ('2.00', '5.00'), '20.00': ('5.00', '10.00'), '30.00': ('10.00', '15.00'),
|
||||
'60.00': ('15.00', '30.00'), '100.00': ('25.00', '50.00'), '200.00': ('50.00', '100.00'),
|
||||
'400.00': ('100.00', '200.00'), '1000.00': ('250.00', '500.00')}
|
||||
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl', 'LIMIT':'fl' }
|
||||
games = { # base, category
|
||||
"Hold'em" : ('hold','holdem'),
|
||||
'Omaha' : ('hold','omahahi'),
|
||||
'Omaha Hi/Lo' : ('hold','omahahilo'),
|
||||
'Razz' : ('stud','razz'),
|
||||
'RAZZ' : ('stud','razz'),
|
||||
'7 Card Stud' : ('stud','studhi'),
|
||||
'7 Card Stud Hi/Lo' : ('stud','studhilo'),
|
||||
'Badugi' : ('draw','badugi'),
|
||||
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
|
||||
'5 Card Draw' : ('draw','fivedraw')
|
||||
}
|
||||
currencies = { u'€':'EUR', '$':'USD', '':'T$' }
|
||||
|
||||
# Static regexes
|
||||
re_GameInfo = re.compile(u"""
|
||||
|
@ -52,7 +77,9 @@ class PokerStars(HandHistoryConverter):
|
|||
(?P<MIXED>HORSE|8\-Game|HOSE)?\s?\(?
|
||||
(?P<GAME>Hold\'em|Razz|RAZZ|7\sCard\sStud|7\sCard\sStud\sHi/Lo|Omaha|Omaha\sHi/Lo|Badugi|Triple\sDraw\s2\-7\sLowball|5\sCard\sDraw)\s
|
||||
(?P<LIMIT>No\sLimit|Limit|LIMIT|Pot\sLimit)\)?,?\s
|
||||
(-\sLevel\s(?P<LEVEL>[IVXLC]+)\s)?
|
||||
(-\s)?
|
||||
(Match.*)? #TODO: waiting for reply from user as to what this means
|
||||
(Level\s(?P<LEVEL>[IVXLC]+)\s)?
|
||||
\(? # open paren of the stakes
|
||||
(?P<CURRENCY>%(LS)s|)?
|
||||
(?P<SB>[.0-9]+)/(%(LS)s)?
|
||||
|
@ -143,43 +170,20 @@ class PokerStars(HandHistoryConverter):
|
|||
raise FpdbParseError("Unable to recognise gametype from: '%s'" % tmp)
|
||||
|
||||
mg = m.groupdict()
|
||||
# translations from captured groups to fpdb info strings
|
||||
Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'),
|
||||
'0.40': ('0.10', '0.20'), '0.50': ('0.10', '0.25'), '1.00': ('0.25', '0.50'),
|
||||
'2.00': ('0.50', '1.00'), '2': ('0.50', '1.00'), '4' : ('1.00', '2.00'),
|
||||
'4.00': ('1.00', '2.00'), '6': ('1.00', '3.00'), '6.00': ('1.00', '3.00'),
|
||||
'10.00': ('2.00', '5.00'), '20.00': ('5.00', '10.00'), '30.00': ('10.00', '15.00'),
|
||||
'60.00': ('15.00', '30.00'), '100.00': ('25.00', '50.00'), '200.00': ('50.00', '100.00'),
|
||||
'400.00': ('100.00', '200.00'), '1000.00': ('250.00', '500.00')}
|
||||
|
||||
limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl', 'LIMIT':'fl' }
|
||||
games = { # base, category
|
||||
"Hold'em" : ('hold','holdem'),
|
||||
'Omaha' : ('hold','omahahi'),
|
||||
'Omaha Hi/Lo' : ('hold','omahahilo'),
|
||||
'Razz' : ('stud','razz'),
|
||||
'RAZZ' : ('stud','razz'),
|
||||
'7 Card Stud' : ('stud','studhi'),
|
||||
'7 Card Stud Hi/Lo' : ('stud','studhilo'),
|
||||
'Badugi' : ('draw','badugi'),
|
||||
'Triple Draw 2-7 Lowball' : ('draw','27_3draw'),
|
||||
'5 Card Draw' : ('draw','fivedraw')
|
||||
}
|
||||
currencies = { u'€':'EUR', '$':'USD', '':'T$' }
|
||||
# I don't think this is doing what we think. mg will always have all
|
||||
# the expected keys, but the ones that didn't match in the regex will
|
||||
# have a value of None. It is OK if it throws an exception when it
|
||||
# runs across an unknown game or limit or whatever.
|
||||
if 'LIMIT' in mg:
|
||||
info['limitType'] = limits[mg['LIMIT']]
|
||||
info['limitType'] = self.limits[mg['LIMIT']]
|
||||
if 'GAME' in mg:
|
||||
(info['base'], info['category']) = games[mg['GAME']]
|
||||
(info['base'], info['category']) = self.games[mg['GAME']]
|
||||
if 'SB' in mg:
|
||||
info['sb'] = mg['SB']
|
||||
if 'BB' in mg:
|
||||
info['bb'] = mg['BB']
|
||||
if 'CURRENCY' in mg:
|
||||
info['currency'] = currencies[mg['CURRENCY']]
|
||||
info['currency'] = self.currencies[mg['CURRENCY']]
|
||||
|
||||
if 'TOURNO' in mg and mg['TOURNO'] is None:
|
||||
info['type'] = 'ring'
|
||||
|
@ -188,8 +192,8 @@ class PokerStars(HandHistoryConverter):
|
|||
|
||||
if info['limitType'] == 'fl' and info['bb'] is not None and info['type'] == 'ring' and info['base'] != 'stud':
|
||||
try:
|
||||
info['sb'] = Lim_Blinds[mg['BB']][0]
|
||||
info['bb'] = Lim_Blinds[mg['BB']][1]
|
||||
info['sb'] = self.Lim_Blinds[mg['BB']][0]
|
||||
info['bb'] = self.Lim_Blinds[mg['BB']][1]
|
||||
except KeyError:
|
||||
log.error("determineGameType: Lim_Blinds has no lookup for '%s'" % mg['BB'])
|
||||
log.error("determineGameType: Raising FpdbParseError")
|
||||
|
@ -219,34 +223,42 @@ class PokerStars(HandHistoryConverter):
|
|||
#2008/09/07 06:23:14 ET
|
||||
m1 = self.re_DateTime.finditer(info[key])
|
||||
# m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key])
|
||||
datetimestr = "2000/01/01 00:00:00" # default used if time not found (stops import crashing, but handstart will be wrong)
|
||||
datetimestr = "2000/01/01 00:00:00" # default used if time not found (stops import crashing, but startTime will be wrong)
|
||||
for a in m1:
|
||||
datetimestr = "%s/%s/%s %s:%s:%s" % (a.group('Y'), a.group('M'),a.group('D'),a.group('H'),a.group('MIN'),a.group('S'))
|
||||
#tz = a.group('TZ') # just assume ET??
|
||||
#print " tz = ", tz, " datetime =", datetimestr
|
||||
hand.starttime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
|
||||
# approximate rules for ET daylight savings time:
|
||||
if ( hand.starttime.month == 12 # all of Dec
|
||||
or (hand.starttime.month == 11 and hand.starttime.day > 4) # and most of November
|
||||
or hand.starttime.month < 3 # and all of Jan/Feb
|
||||
or (hand.starttime.month == 3 and hand.starttime.day < 11) ): # and 1st 10 days of March
|
||||
offset = datetime.timedelta(hours=5) # are EST: assume 5 hour offset (ET without daylight saving)
|
||||
else:
|
||||
offset = datetime.timedelta(hours=4) # rest is EDT: assume 4 hour offset (ET with daylight saving)
|
||||
# adjust time into UTC:
|
||||
hand.starttime = hand.starttime + offset
|
||||
#print " tz = %s start = %s" % (tz, str(hand.starttime))
|
||||
hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
|
||||
hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC")
|
||||
if key == 'HID':
|
||||
hand.handid = info[key]
|
||||
if key == 'TOURNO':
|
||||
hand.tourNo = info[key]
|
||||
if key == 'BUYIN':
|
||||
if info[key] == 'Freeroll':
|
||||
hand.buyin = '$0+$0'
|
||||
else:
|
||||
#FIXME: The key looks like: '€0.82+€0.18 EUR'
|
||||
# This should be parsed properly and used
|
||||
hand.buyin = info[key]
|
||||
if hand.tourNo!=None:
|
||||
if info[key] == 'Freeroll':
|
||||
hand.buyin = 0
|
||||
hand.fee = 0
|
||||
hand.buyinCurrency = "FREE"
|
||||
else:
|
||||
#print "info[key]:",info[key]
|
||||
if info[key].find("$")!=-1:
|
||||
hand.buyinCurrency="USD"
|
||||
elif info[key].find(u"€")!=-1:
|
||||
hand.buyinCurrency="EUR"
|
||||
elif info[key].find("FPP")!=-1:
|
||||
hand.buyinCurrency="PSFP"
|
||||
else:
|
||||
raise FpdbParseError("failed to detect currency") #FIXME: handle other currencies, FPP, play money
|
||||
|
||||
if hand.buyinCurrency=="USD" or hand.buyinCurrency=="EUR":
|
||||
info[key]=info[key][:-4]
|
||||
middle=info[key].find("+")
|
||||
hand.buyin = int(100*Decimal(info[key][1:middle]))
|
||||
hand.fee = int(100*Decimal(info[key][middle+2:]))
|
||||
elif hand.buyinCurrency=="PSFP":
|
||||
hand.buyin = int(Decimal(info[key][0:-3]))
|
||||
hand.fee = 0
|
||||
if key == 'LEVEL':
|
||||
hand.level = info[key]
|
||||
|
||||
|
@ -265,7 +277,7 @@ class PokerStars(HandHistoryConverter):
|
|||
if key == 'PLAY' and info['PLAY'] is not None:
|
||||
# hand.currency = 'play' # overrides previously set value
|
||||
hand.gametype['currency'] = 'play'
|
||||
|
||||
|
||||
def readButton(self, hand):
|
||||
m = self.re_Button.search(hand.handText)
|
||||
if m:
|
||||
|
|
514
pyfpdb/SQL.py
514
pyfpdb/SQL.py
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Manage collecting and formatting of stats and tooltips.
|
||||
|
@ -760,38 +760,39 @@ def ffreq4(stat_dict, player):
|
|||
|
||||
if __name__== "__main__":
|
||||
c = Configuration.Config()
|
||||
db_connection = Database.Database(c)
|
||||
h = db_connection.get_last_hand()
|
||||
stat_dict = db_connection.get_stats_from_hand(h, "ring")
|
||||
#TODO: restore the below code. somehow it creates a version 119 DB but commenting this out makes it print a stat list
|
||||
#db_connection = Database.Database(c)
|
||||
#h = db_connection.get_last_hand()
|
||||
#stat_dict = db_connection.get_stats_from_hand(h, "ring")
|
||||
|
||||
for player in stat_dict.keys():
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'pfr')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wtsd')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'profit100')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'saw_f')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'n')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq1')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq2')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq3')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq4')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb1')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb2')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb3')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb4')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq1')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq2')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq3')
|
||||
print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq4')
|
||||
print "\n"
|
||||
#for player in stat_dict.keys():
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'vpip')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'pfr')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'wtsd')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'profit100')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'saw_f')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'n')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'fold_f')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'wmsd')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'steal')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_SB_steal')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'f_BB_steal')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'three_B')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'WMsF')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq1')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq2')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq3')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq4')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'a_freq_123')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb1')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb2')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb3')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'cb4')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq1')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq2')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq3')
|
||||
#print "player = ", player, do_stat(stat_dict, player = player, stat = 'ffreq4')
|
||||
#print "\n"
|
||||
|
||||
print "\n\nLegal stats:"
|
||||
print "(add _0 to name to display with 0 decimal places, _1 to display with 1, etc)\n"
|
||||
|
@ -804,5 +805,5 @@ if __name__== "__main__":
|
|||
# print " <pu_stat pu_stat_name = \"%s\"> </pu_stat>" % (attr)
|
||||
print
|
||||
|
||||
db_connection.close_connection
|
||||
#db_connection.close_connection
|
||||
|
||||
|
|
66
pyfpdb/Summaries.py
Normal file
66
pyfpdb/Summaries.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
"""This file is to fetch summaries through IMAP and pass them on to the appropriate parser"""
|
||||
#see http://docs.python.org/library/imaplib.html for the python interface
|
||||
#see http://tools.ietf.org/html/rfc2060#section-6.4.4 for IMAP4 search criteria
|
||||
|
||||
import sys
|
||||
from imaplib import IMAP4_SSL
|
||||
from Summaries import Summaries
|
||||
from PokerStarsSummaries import PokerStarsSummaries
|
||||
|
||||
#TODO: move all these into the config file. until then usage is: ./ImapSummaries.py YourImapHost YourImapUser YourImapPw
|
||||
configHost=sys.argv[1]
|
||||
configUser=sys.argv[2]
|
||||
configPw=sys.argv[3]
|
||||
|
||||
server = IMAP4_SSL(configHost) #TODO: optionally non-SSL
|
||||
response = server.login(configUser, configPw) #TODO catch authentication error
|
||||
#print "response to logging in:",response
|
||||
#print "server.list():",server.list() #prints list of folders
|
||||
|
||||
response = server.select("INBOX")
|
||||
#print "response to selecting INBOX:",response
|
||||
if response[0]!="OK":
|
||||
raise error #TODO: show error message
|
||||
|
||||
neededMessages=[]
|
||||
response, searchData = server.search(None, "SUBJECT", "PokerStars Tournament History Request")
|
||||
for messageNumber in searchData[0].split(" "):
|
||||
response, headerData = server.fetch(messageNumber, "(BODY[HEADER.FIELDS (SUBJECT)])")
|
||||
#print "response to fetch subject:",response
|
||||
if response!="OK":
|
||||
raise error #TODO: show error message
|
||||
if headerData[1].find("Subject: PokerStars Tournament History Request - Last x")!=1:
|
||||
neededMessages.append((messageNumber, "PS")
|
||||
|
||||
tourneys=[]
|
||||
if len(neededMessages)==0:
|
||||
raise error #TODO: show error message
|
||||
for messageData in neededMessages:
|
||||
response, bodyData = server.fetch(messageData[0], "(UID BODY[TEXT])")
|
||||
if response!="OK":
|
||||
raise error #TODO: show error message
|
||||
if messageData[0]=="PS":
|
||||
tourneys.append(PokerStarsSummaries.PokerStarsSummaries(bodyData)
|
||||
|
||||
for tourney in tourneys:
|
||||
print "tourney:",tourney
|
||||
|
||||
server.close()
|
||||
server.logout()
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2009-2010 Eric Blade, and the FPDB team.
|
||||
|
@ -51,7 +51,7 @@ class SummaryParser(htmllib.HTMLParser): # derive new HTML parser
|
|||
self.nextPool = False
|
||||
self.TourneyPool = None
|
||||
self.nextPlayers = False
|
||||
self.TourneyPlayers = None
|
||||
self.TourneysPlayers = None
|
||||
self.nextAllowRebuys = False
|
||||
self.TourneyRebuys = None
|
||||
self.parseResultsA = False
|
||||
|
@ -134,7 +134,7 @@ class SummaryParser(htmllib.HTMLParser): # derive new HTML parser
|
|||
if not self.nextPlayers and x == "Player Count:":
|
||||
self.nextPlayers = True
|
||||
elif self.nextPlayers:
|
||||
self.TourneyPlayers = x
|
||||
self.TourneysPlayers = x
|
||||
self.nextPlayers = False
|
||||
|
||||
if not self.nextAllowRebuys and x == "Rebuys possible?:":
|
||||
|
@ -179,7 +179,7 @@ class EverleafSummary:
|
|||
print "site=",self.parser.SiteName, "tourneyname=", self.parser.TourneyName, "tourneyid=", self.parser.TourneyId
|
||||
print "start time=",self.parser.TourneyStartTime, "end time=",self.parser.TourneyEndTime
|
||||
print "structure=", self.parser.TourneyStructure, "game type=",self.parser.TourneyGameType
|
||||
print "buy-in=", self.parser.TourneyBuyIn, "rebuys=", self.parser.TourneyRebuys, "total players=", self.parser.TourneyPlayers, "pool=", self.parser.TourneyPool
|
||||
print "buy-in=", self.parser.TourneyBuyIn, "rebuys=", self.parser.TourneyRebuys, "total players=", self.parser.TourneysPlayers, "pool=", self.parser.TourneyPool
|
||||
print "results=", self.parser.Results
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Discover_TableWindow.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Discover_Tables.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tables_Demo.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""TourneyTracker.py
|
||||
Based on HUD_main .. who knows if we want to actually use this or not
|
||||
|
@ -87,7 +87,7 @@ class Tournament:
|
|||
self.buyin = summary.parser.TourneyBuyIn # need to remember to parse the Fee out of this and move it to self.fee
|
||||
self.rebuys = (summary.parser.TourneyRebuys == "yes")
|
||||
self.prizepool = summary.parser.TourneyPool
|
||||
self.numplayers = summary.parser.TourneyPlayers
|
||||
self.numplayers = summary.parser.TourneysPlayers
|
||||
|
||||
self.openwindow() # let's start by getting any info we need.. meh
|
||||
|
||||
|
|
540
pyfpdb/TourneyFilters.py
Normal file
540
pyfpdb/TourneyFilters.py
Normal file
|
@ -0,0 +1,540 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2010 Steffen Schaumburg
|
||||
#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.
|
||||
|
||||
import threading
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import gobject
|
||||
#import os
|
||||
#import sys
|
||||
#from optparse import OptionParser
|
||||
from time import gmtime, mktime, strftime, strptime
|
||||
#import pokereval
|
||||
|
||||
import logging #logging has been set up in fpdb.py or HUD_main.py, use their settings:
|
||||
log = logging.getLogger("filter")
|
||||
|
||||
|
||||
#import Configuration
|
||||
#import Database
|
||||
#import SQL
|
||||
import Charset
|
||||
import Filters
|
||||
|
||||
class TourneyFilters(Filters.Filters):
|
||||
def __init__(self, db, config, qdict, display = {}, debug=True):
|
||||
self.debug = debug
|
||||
self.db = db
|
||||
self.cursor = db.cursor
|
||||
self.sql = db.sql
|
||||
self.conf = db.config
|
||||
self.display = display
|
||||
|
||||
self.filterText = {'playerstitle':'Hero:', 'sitestitle':'Sites:', 'seatstitle':'Number of Players:',
|
||||
'seatsbetween':'Between:', 'seatsand':'And:', 'datestitle':'Date:',
|
||||
'tourneyTypesTitle':'Tourney Type'}
|
||||
|
||||
gen = self.conf.get_general_params()
|
||||
self.day_start = 0
|
||||
if 'day_start' in gen:
|
||||
self.day_start = float(gen['day_start'])
|
||||
|
||||
# Outer Packing box
|
||||
self.mainVBox = gtk.VBox(False, 0)
|
||||
|
||||
self.label = {}
|
||||
self.callback = {}
|
||||
|
||||
self.make_filter()
|
||||
#end def __init__
|
||||
|
||||
def __calendar_dialog(self, widget, entry):
|
||||
d = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
d.set_title('Pick a date')
|
||||
|
||||
vb = gtk.VBox()
|
||||
cal = gtk.Calendar()
|
||||
vb.pack_start(cal, expand=False, padding=0)
|
||||
|
||||
btn = gtk.Button('Done')
|
||||
btn.connect('clicked', self.__get_date, cal, entry, d)
|
||||
|
||||
vb.pack_start(btn, expand=False, padding=4)
|
||||
|
||||
d.add(vb)
|
||||
d.set_position(gtk.WIN_POS_MOUSE)
|
||||
d.show_all()
|
||||
#end def __calendar_dialog
|
||||
|
||||
def __clear_dates(self, w):
|
||||
self.start_date.set_text('')
|
||||
self.end_date.set_text('')
|
||||
#end def __clear_dates
|
||||
|
||||
def __get_dates(self):
|
||||
# self.day_start gives user's start of day in hours
|
||||
offset = int(self.day_start * 3600) # calc day_start in seconds
|
||||
|
||||
t1 = self.start_date.get_text()
|
||||
t2 = self.end_date.get_text()
|
||||
|
||||
if t1 == '':
|
||||
t1 = '1970-01-02'
|
||||
if t2 == '':
|
||||
t2 = '2020-12-12'
|
||||
|
||||
s1 = strptime(t1, "%Y-%m-%d") # make time_struct
|
||||
s2 = strptime(t2, "%Y-%m-%d")
|
||||
e1 = mktime(s1) + offset # s1 is localtime, but returned time since epoch is UTC, then add the
|
||||
e2 = mktime(s2) + offset # s2 is localtime, but returned time since epoch is UTC
|
||||
e2 = e2 + 24 * 3600 - 1 # date test is inclusive, so add 23h 59m 59s to e2
|
||||
|
||||
adj_t1 = strftime("%Y-%m-%d %H:%M:%S", gmtime(e1)) # make adjusted string including time
|
||||
adj_t2 = strftime("%Y-%m-%d %H:%M:%S", gmtime(e2))
|
||||
log.info("t1="+t1+" adj_t1="+adj_t1+'.')
|
||||
|
||||
return (adj_t1, adj_t2)
|
||||
#end def __get_dates
|
||||
|
||||
def __refresh(self, widget, entry):
|
||||
for w in self.mainVBox.get_children():
|
||||
w.destroy()
|
||||
self.make_filter()
|
||||
#end def __refresh
|
||||
|
||||
def __set_hero_name(self, w, site):
|
||||
_name = w.get_text()
|
||||
# get_text() returns a str but we want internal variables to be unicode:
|
||||
_guiname = unicode(_name)
|
||||
self.heroes[site] = _guiname
|
||||
#log.debug("setting heroes[%s]: %s"%(site, self.heroes[site]))
|
||||
#end def __set_hero_name
|
||||
|
||||
def __set_num_tourneys(self, w, val):
|
||||
try:
|
||||
self.numTourneys = int(w.get_text())
|
||||
except:
|
||||
self.numTourneys = 0
|
||||
print "setting numTourneys:", self.numTourneys
|
||||
#end def __set_num_tourneys
|
||||
|
||||
def __set_seat_select(self, w, seat):
|
||||
#print "__set_seat_select: seat =", seat, "active =", w.get_active()
|
||||
self.seats[seat] = w.get_active()
|
||||
log.debug( "self.seats[%s] set to %s" %(seat, self.seats[seat]) )
|
||||
#end def __set_seat_select
|
||||
|
||||
def __set_site_select(self, w, site):
|
||||
#print w.get_active()
|
||||
self.sites[site] = w.get_active()
|
||||
log.debug("self.sites[%s] set to %s" %(site, self.sites[site]))
|
||||
#end def __set_site_select
|
||||
|
||||
def __set_tourney_type_select(self, w, tourneyType):
|
||||
#print w.get_active()
|
||||
self.tourneyTypes[tourneyType] = w.get_active()
|
||||
log.debug("self.tourney_types[%s] set to %s" %(tourneyType, self.tourneyTypes[tourneyType]))
|
||||
#end def __set_tourney_type_select
|
||||
|
||||
def __toggle_box(self, widget, entry):
|
||||
if self.boxes[entry].props.visible:
|
||||
self.boxes[entry].hide()
|
||||
widget.set_label("show")
|
||||
else:
|
||||
self.boxes[entry].show()
|
||||
widget.set_label("hide")
|
||||
#end def __toggle_box
|
||||
|
||||
def createPlayerLine(self, hbox, site, player):
|
||||
log.debug('add:"%s"' % player)
|
||||
label = gtk.Label(site +" id:")
|
||||
hbox.pack_start(label, False, False, 3)
|
||||
|
||||
pname = gtk.Entry()
|
||||
pname.set_text(player)
|
||||
pname.set_width_chars(20)
|
||||
hbox.pack_start(pname, False, True, 0)
|
||||
#pname.connect("changed", self.__set_hero_name, site)
|
||||
|
||||
# Added EntryCompletion but maybe comboBoxEntry is more flexible? (e.g. multiple choices)
|
||||
completion = gtk.EntryCompletion()
|
||||
pname.set_completion(completion)
|
||||
liststore = gtk.ListStore(gobject.TYPE_STRING)
|
||||
completion.set_model(liststore)
|
||||
completion.set_text_column(0)
|
||||
names = self.db.get_player_names(self.conf, self.siteid[site]) # (config=self.conf, site_id=None, like_player_name="%")
|
||||
for n in names: # list of single-element "tuples"
|
||||
_n = Charset.to_gui(n[0])
|
||||
_nt = (_n, )
|
||||
liststore.append(_nt)
|
||||
|
||||
self.__set_hero_name(pname, site)
|
||||
#end def createPlayerLine
|
||||
|
||||
def createSiteLine(self, hbox, site):
|
||||
cb = gtk.CheckButton(site)
|
||||
cb.connect('clicked', self.__set_site_select, site)
|
||||
cb.set_active(True)
|
||||
hbox.pack_start(cb, False, False, 0)
|
||||
#end def createSiteLine
|
||||
|
||||
def createTourneyTypeLine(self, hbox, tourneyType):
|
||||
cb = gtk.CheckButton(str(tourneyType))
|
||||
cb.connect('clicked', self.__set_tourney_type_select, tourneyType)
|
||||
hbox.pack_start(cb, False, False, 0)
|
||||
cb.set_active(True)
|
||||
#end def createTourneyTypeLine
|
||||
|
||||
def fillDateFrame(self, vbox):
|
||||
# Hat tip to Mika Bostrom - calendar code comes from PokerStats
|
||||
top_hbox = gtk.HBox(False, 0)
|
||||
vbox.pack_start(top_hbox, False, False, 0)
|
||||
lbl_title = gtk.Label(self.filterText['datestitle'])
|
||||
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
|
||||
top_hbox.pack_start(lbl_title, expand=True, padding=3)
|
||||
showb = gtk.Button(label="hide", stock=None, use_underline=True)
|
||||
showb.set_alignment(xalign=1.0, yalign=0.5)
|
||||
showb.connect('clicked', self.__toggle_box, 'dates')
|
||||
top_hbox.pack_start(showb, expand=False, padding=1)
|
||||
|
||||
vbox1 = gtk.VBox(False, 0)
|
||||
vbox.pack_start(vbox1, False, False, 0)
|
||||
self.boxes['dates'] = vbox1
|
||||
|
||||
hbox = gtk.HBox()
|
||||
vbox1.pack_start(hbox, False, True, 0)
|
||||
|
||||
lbl_start = gtk.Label('From:')
|
||||
|
||||
btn_start = gtk.Button()
|
||||
btn_start.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
|
||||
btn_start.connect('clicked', self.__calendar_dialog, self.start_date)
|
||||
|
||||
hbox.pack_start(lbl_start, expand=False, padding=3)
|
||||
hbox.pack_start(btn_start, expand=False, padding=3)
|
||||
hbox.pack_start(self.start_date, expand=False, padding=2)
|
||||
|
||||
#New row for end date
|
||||
hbox = gtk.HBox()
|
||||
vbox1.pack_start(hbox, False, True, 0)
|
||||
|
||||
lbl_end = gtk.Label(' To:')
|
||||
btn_end = gtk.Button()
|
||||
btn_end.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
|
||||
btn_end.connect('clicked', self.__calendar_dialog, self.end_date)
|
||||
|
||||
btn_clear = gtk.Button(label=' Clear Dates ')
|
||||
btn_clear.connect('clicked', self.__clear_dates)
|
||||
|
||||
hbox.pack_start(lbl_end, expand=False, padding=3)
|
||||
hbox.pack_start(btn_end, expand=False, padding=3)
|
||||
hbox.pack_start(self.end_date, expand=False, padding=2)
|
||||
|
||||
hbox.pack_start(btn_clear, expand=False, padding=15)
|
||||
#end def fillDateFrame
|
||||
|
||||
def fillPlayerFrame(self, vbox, display):
|
||||
top_hbox = gtk.HBox(False, 0)
|
||||
vbox.pack_start(top_hbox, False, False, 0)
|
||||
lbl_title = gtk.Label(self.filterText['playerstitle'])
|
||||
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
|
||||
top_hbox.pack_start(lbl_title, expand=True, padding=3)
|
||||
showb = gtk.Button(label="refresh", stock=None, use_underline=True)
|
||||
showb.set_alignment(xalign=1.0, yalign=0.5)
|
||||
showb.connect('clicked', self.__refresh, 'players')
|
||||
|
||||
vbox1 = gtk.VBox(False, 0)
|
||||
vbox.pack_start(vbox1, False, False, 0)
|
||||
self.boxes['players'] = vbox1
|
||||
|
||||
for site in self.conf.get_supported_sites():
|
||||
hBox = gtk.HBox(False, 0)
|
||||
vbox1.pack_start(hBox, False, True, 0)
|
||||
|
||||
player = self.conf.supported_sites[site].screen_name
|
||||
_pname = Charset.to_gui(player)
|
||||
self.createPlayerLine(hBox, site, _pname)
|
||||
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox1.pack_start(hbox, False, False, 0)
|
||||
#cb = gtk.CheckButton(self.filterText['groupsall'])
|
||||
#cb.connect('clicked', self.__set_group_select, 'allplayers')
|
||||
#hbox.pack_start(cb, False, False, 0)
|
||||
#self.sbGroups['allplayers'] = cb
|
||||
#self.groups['allplayers'] = False
|
||||
|
||||
#lbl = gtk.Label('Min # Hands:')
|
||||
#lbl.set_alignment(xalign=1.0, yalign=0.5)
|
||||
#hbox.pack_start(lbl, expand=True, padding=3)
|
||||
|
||||
#phands = gtk.Entry()
|
||||
#phands.set_text('0')
|
||||
#phands.set_width_chars(8)
|
||||
#hbox.pack_start(phands, False, False, 0)
|
||||
#phands.connect("changed", self.__set_num_hands, site)
|
||||
|
||||
top_hbox.pack_start(showb, expand=False, padding=1)
|
||||
#end def fillPlayerFrame
|
||||
|
||||
def fillSeatsFrame(self, vbox, display):
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox.pack_start(hbox, False, False, 0)
|
||||
lbl_title = gtk.Label(self.filterText['seatstitle'])
|
||||
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
|
||||
hbox.pack_start(lbl_title, expand=True, padding=3)
|
||||
showb = gtk.Button(label="hide", stock=None, use_underline=True)
|
||||
showb.set_alignment(xalign=1.0, yalign=0.5)
|
||||
showb.connect('clicked', self.__toggle_box, 'seats')
|
||||
hbox.pack_start(showb, expand=False, padding=1)
|
||||
|
||||
vbox1 = gtk.VBox(False, 0)
|
||||
vbox.pack_start(vbox1, False, False, 0)
|
||||
self.boxes['seats'] = vbox1
|
||||
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox1.pack_start(hbox, False, True, 0)
|
||||
|
||||
lbl_from = gtk.Label(self.filterText['seatsbetween'])
|
||||
lbl_to = gtk.Label(self.filterText['seatsand'])
|
||||
adj1 = gtk.Adjustment(value=2, lower=2, upper=10, step_incr=1, page_incr=1, page_size=0)
|
||||
sb1 = gtk.SpinButton(adjustment=adj1, climb_rate=0.0, digits=0)
|
||||
adj2 = gtk.Adjustment(value=10, lower=2, upper=10, step_incr=1, page_incr=1, page_size=0)
|
||||
sb2 = gtk.SpinButton(adjustment=adj2, climb_rate=0.0, digits=0)
|
||||
|
||||
hbox.pack_start(lbl_from, expand=False, padding=3)
|
||||
hbox.pack_start(sb1, False, False, 0)
|
||||
hbox.pack_start(lbl_to, expand=False, padding=3)
|
||||
hbox.pack_start(sb2, False, False, 0)
|
||||
|
||||
self.sbSeats['from'] = sb1
|
||||
self.sbSeats['to'] = sb2
|
||||
#end def fillSeatsFrame
|
||||
|
||||
def fillSitesFrame(self, vbox):
|
||||
top_hbox = gtk.HBox(False, 0)
|
||||
top_hbox.show()
|
||||
vbox.pack_start(top_hbox, False, False, 0)
|
||||
|
||||
lbl_title = gtk.Label(self.filterText['sitestitle'])
|
||||
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
|
||||
top_hbox.pack_start(lbl_title, expand=True, padding=3)
|
||||
|
||||
showb = gtk.Button(label="hide", stock=None, use_underline=True)
|
||||
showb.set_alignment(xalign=1.0, yalign=0.5)
|
||||
showb.connect('clicked', self.__toggle_box, 'sites')
|
||||
showb.show()
|
||||
top_hbox.pack_start(showb, expand=False, padding=1)
|
||||
|
||||
vbox1 = gtk.VBox(False, 0)
|
||||
self.boxes['sites'] = vbox1
|
||||
vbox.pack_start(vbox1, False, False, 0)
|
||||
|
||||
for site in self.conf.get_supported_sites():
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox1.pack_start(hbox, False, True, 0)
|
||||
self.createSiteLine(hbox, site)
|
||||
#end def fillSitesFrame
|
||||
|
||||
def fillTourneyTypesFrame(self, vbox):
|
||||
top_hbox = gtk.HBox(False, 0)
|
||||
vbox.pack_start(top_hbox, False, False, 0)
|
||||
lbl_title = gtk.Label(self.filterText['tourneyTypesTitle'])
|
||||
lbl_title.set_alignment(xalign=0.0, yalign=0.5)
|
||||
top_hbox.pack_start(lbl_title, expand=True, padding=3)
|
||||
showb = gtk.Button(label="hide", stock=None, use_underline=True)
|
||||
showb.set_alignment(xalign=1.0, yalign=0.5)
|
||||
showb.connect('clicked', self.__toggle_box, 'tourneyTypes')
|
||||
top_hbox.pack_start(showb, expand=False, padding=1)
|
||||
|
||||
vbox1 = gtk.VBox(False, 0)
|
||||
vbox.pack_start(vbox1, False, False, 0)
|
||||
self.boxes['tourneyTypes'] = vbox1
|
||||
|
||||
result = self.db.getTourneyTypesIds()
|
||||
if len(result) >= 1:
|
||||
for line in result:
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox1.pack_start(hbox, False, True, 0)
|
||||
self.createTourneyTypeLine(hbox, line[0])
|
||||
else:
|
||||
print "INFO: No tourney types returned from database"
|
||||
log.info("No tourney types returned from database")
|
||||
#end def fillTourneyTypesFrame
|
||||
|
||||
def getDates(self):
|
||||
return self.__get_dates()
|
||||
#end def getDates
|
||||
|
||||
def getHeroes(self):
|
||||
return self.heroes
|
||||
#end def getHeroes
|
||||
|
||||
def getNumTourneys(self):
|
||||
return self.numTourneys
|
||||
#end def getNumTourneys
|
||||
|
||||
def getSeats(self):
|
||||
if 'from' in self.sbSeats:
|
||||
self.seats['from'] = self.sbSeats['from'].get_value_as_int()
|
||||
if 'to' in self.sbSeats:
|
||||
self.seats['to'] = self.sbSeats['to'].get_value_as_int()
|
||||
return self.seats
|
||||
#end def getSeats
|
||||
|
||||
def getSiteIds(self):
|
||||
return self.siteid
|
||||
#end def getSiteIds
|
||||
|
||||
def getSites(self):
|
||||
return self.sites
|
||||
#end def getSites
|
||||
|
||||
def getTourneyTypes(self):
|
||||
return self.tourneyTypes
|
||||
#end def getTourneyTypes
|
||||
|
||||
def get_vbox(self):
|
||||
"""returns the vbox of this thread"""
|
||||
return self.mainVBox
|
||||
#end def get_vbox
|
||||
|
||||
def make_filter(self):
|
||||
self.tourneyTypes = {}
|
||||
#self.tourneys = {}
|
||||
self.sites = {}
|
||||
self.seats = {}
|
||||
self.siteid = {}
|
||||
self.heroes = {}
|
||||
self.boxes = {}
|
||||
|
||||
for site in self.conf.get_supported_sites():
|
||||
#Get db site id for filtering later
|
||||
self.cursor.execute(self.sql.query['getSiteId'], (site,))
|
||||
result = self.db.cursor.fetchall()
|
||||
if len(result) == 1:
|
||||
self.siteid[site] = result[0][0]
|
||||
else:
|
||||
print "Either 0 or more than one site matched (%s) - EEK" % site
|
||||
|
||||
# For use in date ranges.
|
||||
self.start_date = gtk.Entry(max=12)
|
||||
self.end_date = gtk.Entry(max=12)
|
||||
self.start_date.set_property('editable', False)
|
||||
self.end_date.set_property('editable', False)
|
||||
|
||||
# For use in groups etc
|
||||
#self.sbGroups = {}
|
||||
self.numTourneys = 0
|
||||
|
||||
playerFrame = gtk.Frame()
|
||||
playerFrame.set_label_align(0.0, 0.0)
|
||||
vbox = gtk.VBox(False, 0)
|
||||
|
||||
self.fillPlayerFrame(vbox, self.display)
|
||||
playerFrame.add(vbox)
|
||||
|
||||
sitesFrame = gtk.Frame()
|
||||
sitesFrame.set_label_align(0.0, 0.0)
|
||||
vbox = gtk.VBox(False, 0)
|
||||
|
||||
self.fillSitesFrame(vbox)
|
||||
sitesFrame.add(vbox)
|
||||
|
||||
# Tourney types
|
||||
tourneyTypesFrame = gtk.Frame()
|
||||
tourneyTypesFrame.set_label_align(0.0, 0.0)
|
||||
tourneyTypesFrame.show()
|
||||
vbox = gtk.VBox(False, 0)
|
||||
|
||||
self.fillTourneyTypesFrame(vbox)
|
||||
tourneyTypesFrame.add(vbox)
|
||||
|
||||
# Seats
|
||||
seatsFrame = gtk.Frame()
|
||||
seatsFrame.show()
|
||||
vbox = gtk.VBox(False, 0)
|
||||
self.sbSeats = {}
|
||||
|
||||
self.fillSeatsFrame(vbox, self.display)
|
||||
seatsFrame.add(vbox)
|
||||
|
||||
# Date
|
||||
dateFrame = gtk.Frame()
|
||||
dateFrame.set_label_align(0.0, 0.0)
|
||||
dateFrame.show()
|
||||
vbox = gtk.VBox(False, 0)
|
||||
|
||||
self.fillDateFrame(vbox)
|
||||
dateFrame.add(vbox)
|
||||
|
||||
# Buttons
|
||||
#self.Button1=gtk.Button("Unnamed 1")
|
||||
#self.Button1.set_sensitive(False)
|
||||
|
||||
self.Button2=gtk.Button("Unnamed 2")
|
||||
self.Button2.set_sensitive(False)
|
||||
|
||||
self.mainVBox.add(playerFrame)
|
||||
self.mainVBox.add(sitesFrame)
|
||||
self.mainVBox.add(seatsFrame)
|
||||
self.mainVBox.add(dateFrame)
|
||||
#self.mainVBox.add(self.Button1)
|
||||
self.mainVBox.add(self.Button2)
|
||||
|
||||
self.mainVBox.show_all()
|
||||
|
||||
# Should do this cleaner
|
||||
if "Heroes" not in self.display or self.display["Heroes"] == False:
|
||||
playerFrame.hide()
|
||||
if "Sites" not in self.display or self.display["Sites"] == False:
|
||||
sitesFrame.hide()
|
||||
if "Seats" not in self.display or self.display["Seats"] == False:
|
||||
seatsFrame.hide()
|
||||
if "Dates" not in self.display or self.display["Dates"] == False:
|
||||
dateFrame.hide()
|
||||
#if "Button1" not in self.display or self.display["Button1"] == False:
|
||||
# self.Button1.hide()
|
||||
if "Button2" not in self.display or self.display["Button2"] == False:
|
||||
self.Button2.hide()
|
||||
|
||||
#if 'button1' in self.label and self.label['button1']:
|
||||
# self.Button1.set_label( self.label['button1'] )
|
||||
if 'button2' in self.label and self.label['button2']:
|
||||
self.Button2.set_label( self.label['button2'] )
|
||||
#if 'button1' in self.callback and self.callback['button1']:
|
||||
# self.Button1.connect("clicked", self.callback['button1'], "clicked")
|
||||
# self.Button1.set_sensitive(True)
|
||||
if 'button2' in self.callback and self.callback['button2']:
|
||||
self.Button2.connect("clicked", self.callback['button2'], "clicked")
|
||||
self.Button2.set_sensitive(True)
|
||||
|
||||
# make sure any locks on db are released:
|
||||
self.db.rollback()
|
||||
#end def make_filter
|
||||
|
||||
def registerButton2Name(self, title):
|
||||
self.Button2.set_label(title)
|
||||
self.label['button2'] = title
|
||||
#end def registerButton2Name
|
||||
|
||||
def registerButton2Callback(self, callback):
|
||||
self.Button2.connect("clicked", callback, "clicked")
|
||||
self.Button2.set_sensitive(True)
|
||||
self.callback['button2'] = callback
|
||||
#end def registerButton2Callback
|
||||
#end class TourneyFilters
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Stephane Alessio
|
||||
|
@ -15,6 +15,8 @@
|
|||
#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.
|
||||
|
||||
"""parses and stores summary sections from e.g. eMail or summary files"""
|
||||
|
||||
# TODO: check to keep only the needed modules
|
||||
|
||||
import re
|
||||
|
@ -34,7 +36,7 @@ import Card
|
|||
|
||||
log = logging.getLogger("parser")
|
||||
|
||||
class Tourney(object):
|
||||
class TourneySummary(object):
|
||||
|
||||
################################################################
|
||||
# Class Variables
|
||||
|
@ -45,63 +47,87 @@ class Tourney(object):
|
|||
SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
|
||||
|
||||
|
||||
def __init__(self, sitename, gametype, summaryText, builtFrom = "HHC"):
|
||||
self.sitename = sitename
|
||||
self.siteId = self.SITEIDS[sitename]
|
||||
self.gametype = gametype
|
||||
self.starttime = None
|
||||
self.endtime = None
|
||||
def __init__(self, db, config, siteName, summaryText, builtFrom = "HHC"):
|
||||
self.db = db
|
||||
self.config = config
|
||||
self.siteName = siteName
|
||||
self.siteId = self.SITEIDS[siteName]
|
||||
|
||||
self.summaryText = summaryText
|
||||
self.tourneyName = None
|
||||
self.tourneyTypeId = None
|
||||
self.tourneyId = None
|
||||
self.startTime = None
|
||||
self.endTime = None
|
||||
self.tourNo = None
|
||||
self.currency = None
|
||||
self.buyin = None
|
||||
self.fee = None # the Database code is looking for this one .. ?
|
||||
self.fee = None
|
||||
self.hero = None
|
||||
self.maxseats = None
|
||||
self.entries = 0
|
||||
self.speed = "Normal"
|
||||
self.prizepool = None # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??}
|
||||
self.buyInChips = None
|
||||
self.prizepool = 0 # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??}
|
||||
self.buyInChips = 0
|
||||
self.mixed = None
|
||||
self.isRebuy = False
|
||||
self.isAddOn = False
|
||||
self.isKO = False
|
||||
self.isHU = False
|
||||
self.isMatrix = False
|
||||
self.isShootout = False
|
||||
self.matrixMatchId = None # For Matrix tourneys : 1-4 => match tables (traditionnal), 0 => Positional winnings info
|
||||
self.matrixIdProcessed = None
|
||||
self.subTourneyBuyin = None
|
||||
self.subTourneyFee = None
|
||||
self.rebuyChips = 0
|
||||
self.addOnChips = 0
|
||||
self.rebuyAmount = 0
|
||||
self.addOnAmount = 0
|
||||
self.totalRebuys = 0
|
||||
self.totalAddOns = 0
|
||||
self.koBounty = 0
|
||||
self.rebuyChips = None
|
||||
self.addOnChips = None
|
||||
self.rebuyCost = None
|
||||
self.addOnCost = None
|
||||
self.totalRebuyCount = None
|
||||
self.totalAddOnCount = None
|
||||
self.koBounty = None
|
||||
self.tourneyComment = None
|
||||
self.players = []
|
||||
self.isSng = False
|
||||
self.isSatellite = False
|
||||
self.isDoubleOrNothing = False
|
||||
self.guarantee = None
|
||||
self.added = None
|
||||
self.addedCurrency = None
|
||||
self.gametype = {'category':None, 'limitType':None}
|
||||
self.comment = None
|
||||
self.commentTs = None
|
||||
|
||||
# Collections indexed by player names
|
||||
self.finishPositions = {}
|
||||
self.playerIds = {}
|
||||
self.tourneysPlayersIds = {}
|
||||
self.ranks = {}
|
||||
self.winnings = {}
|
||||
self.payinAmounts = {}
|
||||
self.countRebuys = {}
|
||||
self.countAddOns = {}
|
||||
self.countKO = {}
|
||||
self.winningsCurrency = {}
|
||||
self.rebuyCounts = {}
|
||||
self.addOnCounts = {}
|
||||
self.koCounts = {}
|
||||
|
||||
# currency symbol for this summary
|
||||
self.sym = None
|
||||
#self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done
|
||||
|
||||
if builtFrom=="IMAP":
|
||||
self.parseSummary()
|
||||
self.insertOrUpdate()
|
||||
#end def __init__
|
||||
|
||||
def __str__(self):
|
||||
#TODO : Update
|
||||
vars = ( ("SITE", self.sitename),
|
||||
("START TIME", self.starttime),
|
||||
("END TIME", self.endtime),
|
||||
vars = ( ("SITE", self.siteName),
|
||||
("START TIME", self.startTime),
|
||||
("END TIME", self.endTime),
|
||||
("TOURNEY NAME", self.tourneyName),
|
||||
("TOURNEY NO", self.tourNo),
|
||||
("TOURNEY TYPE ID", self.tourneyTypeId),
|
||||
("TOURNEY ID", self.tourneyId),
|
||||
("BUYIN", self.buyin),
|
||||
("FEE", self.fee),
|
||||
("CURRENCY", self.currency),
|
||||
("HERO", self.hero),
|
||||
("MAXSEATS", self.maxseats),
|
||||
("ENTRIES", self.entries),
|
||||
|
@ -109,32 +135,42 @@ class Tourney(object):
|
|||
("PRIZE POOL", self.prizepool),
|
||||
("STARTING CHIP COUNT", self.buyInChips),
|
||||
("MIXED", self.mixed),
|
||||
("REBUY ADDON", self.isRebuy),
|
||||
("REBUY", self.isRebuy),
|
||||
("ADDON", self.isAddOn),
|
||||
("KO", self.isKO),
|
||||
("HU", self.isHU),
|
||||
("MATRIX", self.isMatrix),
|
||||
("MATRIX ID PROCESSED", self.matrixIdProcessed),
|
||||
("SHOOTOUT", self.isShootout),
|
||||
("MATRIX MATCH ID", self.matrixMatchId),
|
||||
("SUB TOURNEY BUY IN", self.subTourneyBuyin),
|
||||
("SUB TOURNEY FEE", self.subTourneyFee),
|
||||
("REBUY CHIPS", self.rebuyChips),
|
||||
("ADDON CHIPS", self.addOnChips),
|
||||
("REBUY AMOUNT", self.rebuyAmount),
|
||||
("ADDON AMOUNT", self.addOnAmount),
|
||||
("TOTAL REBUYS", self.totalRebuys),
|
||||
("TOTAL ADDONS", self.totalAddOns),
|
||||
("REBUY COST", self.rebuyCost),
|
||||
("ADDON COST", self.addOnCost),
|
||||
("TOTAL REBUYS", self.totalRebuyCount),
|
||||
("TOTAL ADDONS", self.totalAddOnCount),
|
||||
("KO BOUNTY", self.koBounty),
|
||||
("TOURNEY COMMENT", self.tourneyComment)
|
||||
("TOURNEY COMMENT", self.tourneyComment),
|
||||
("SNG", self.isSng),
|
||||
("SATELLITE", self.isSatellite),
|
||||
("DOUBLE OR NOTHING", self.isDoubleOrNothing),
|
||||
("GUARANTEE", self.guarantee),
|
||||
("ADDED", self.added),
|
||||
("ADDED CURRENCY", self.addedCurrency),
|
||||
("COMMENT", self.comment),
|
||||
("COMMENT TIMESTAMP", self.commentTs)
|
||||
)
|
||||
|
||||
structs = ( ("GAMETYPE", self.gametype),
|
||||
structs = ( ("PLAYER IDS", self.playerIds),
|
||||
("PLAYERS", self.players),
|
||||
("PAYIN AMOUNTS", self.payinAmounts),
|
||||
("POSITIONS", self.finishPositions),
|
||||
("TOURNEYS PLAYERS IDS", self.tourneysPlayersIds),
|
||||
("RANKS", self.ranks),
|
||||
("WINNINGS", self.winnings),
|
||||
("COUNT REBUYS", self.countRebuys),
|
||||
("COUNT ADDONS", self.countAddOns),
|
||||
("NB OF KO", self.countKO)
|
||||
("WINNINGS CURRENCY", self.winningsCurrency),
|
||||
("COUNT REBUYS", self.rebuyCounts),
|
||||
("COUNT ADDONS", self.addOnCounts),
|
||||
("NB OF KO", self.koCounts)
|
||||
)
|
||||
str = ''
|
||||
for (name, var) in vars:
|
||||
|
@ -143,14 +179,15 @@ class Tourney(object):
|
|||
for (name, struct) in structs:
|
||||
str = str + "\n%s =\n" % name + pprint.pformat(struct, 4)
|
||||
return str
|
||||
#end def __str__
|
||||
|
||||
def parseSummary(self): abstract
|
||||
"""should fill the class variables with the parsed information"""
|
||||
|
||||
def getSummaryText(self):
|
||||
return self.summaryText
|
||||
|
||||
def prepInsert(self, db):
|
||||
pass
|
||||
|
||||
def insert(self, db):
|
||||
|
||||
def insertOrUpdate(self):
|
||||
# First : check all needed info is filled in the object, especially for the initial select
|
||||
|
||||
# Notes on DB Insert
|
||||
|
@ -161,18 +198,29 @@ class Tourney(object):
|
|||
# Starttime may not match the one in the Summary file : HH = time of the first Hand / could be slighltly different from the one in the summary file
|
||||
# Note: If the TourneyNo could be a unique id .... this would really be a relief to deal with matrix matches ==> Ask on the IRC / Ask Fulltilt ??
|
||||
|
||||
dbTourneyTypeId = db.tRecogniseTourneyType(self)
|
||||
logging.debug("Tourney Type ID = %d" % dbTourneyTypeId)
|
||||
dbTourneyId = db.tRecognizeTourney(self, dbTourneyTypeId)
|
||||
logging.debug("Tourney ID = %d" % dbTourneyId)
|
||||
dbTourneysPlayersIds = db.tStoreTourneyPlayers(self, dbTourneyId)
|
||||
logging.debug("TourneysPlayersId = %s" % dbTourneysPlayersIds)
|
||||
db.tUpdateTourneysHandsPlayers(self, dbTourneysPlayersIds, dbTourneyTypeId)
|
||||
logging.debug("tUpdateTourneysHandsPlayers done")
|
||||
logging.debug("Tourney Insert done")
|
||||
for player in self.players:
|
||||
id=self.db.get_player_id(self.config, self.siteName, player)
|
||||
if not id:
|
||||
id=self.db.insertPlayer(unicode(player), self.siteId)
|
||||
self.playerIds.update({player:id})
|
||||
|
||||
#print "TS.insert players",self.players,"playerIds",self.playerIds
|
||||
|
||||
self.buyinCurrency=self.currency
|
||||
self.dbid_pids=self.playerIds #TODO:rename this field in Hand so this silly renaming can be removed
|
||||
|
||||
#print "TS.self before starting insert",self
|
||||
self.tourneyTypeId = self.db.createTourneyType(self)
|
||||
self.db.commit()
|
||||
self.tourneyId = self.db.createOrUpdateTourney(self, "TS")
|
||||
self.db.commit()
|
||||
self.tourneysPlayersIds = self.db.createOrUpdateTourneysPlayers(self, "TS")
|
||||
self.db.commit()
|
||||
|
||||
logging.debug("Tourney Insert/Update done")
|
||||
|
||||
# TO DO : Return what has been done (tourney created, updated, nothing)
|
||||
# ?? stored = 1 if tourney is fully created / duplicates = 1, if everything was already here and correct / partial=1 if some things were already here (between tourney, tourneyPlayers and handsplayers)
|
||||
# ?? stored = 1 if tourney is fully created / duplicates = 1, if everything was already here and correct / partial=1 if some things were already here (between tourney, tourneysPlayers and handsPlayers)
|
||||
# if so, prototypes may need changes to know what has been done or make some kind of dict in Tourney object that could be updated during the insert process to store that information
|
||||
stored = 0
|
||||
duplicates = 0
|
||||
|
@ -181,83 +229,8 @@ class Tourney(object):
|
|||
ttime = 0
|
||||
return (stored, duplicates, partial, errors, ttime)
|
||||
|
||||
|
||||
def old_insert_from_Hand(self, db):
|
||||
""" Function to insert Hand into database
|
||||
Should not commit, and do minimal selects. Callers may want to cache commits
|
||||
db: a connected Database object"""
|
||||
# TODO:
|
||||
# Players - base playerid and siteid tuple
|
||||
sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId)
|
||||
|
||||
#Gametypes
|
||||
gtid = db.getGameTypeId(self.siteId, self.gametype)
|
||||
|
||||
# HudCache data to come from DerivedStats class
|
||||
# HandsActions - all actions for all players for all streets - self.actions
|
||||
# Hands - Summary information of hand indexed by handId - gameinfo
|
||||
#This should be moved to prepInsert
|
||||
hh = {}
|
||||
hh['siteHandNo'] = self.handid
|
||||
hh['handStart'] = self.starttime
|
||||
hh['gameTypeId'] = gtid
|
||||
# seats TINYINT NOT NULL,
|
||||
hh['tableName'] = self.tablename
|
||||
hh['maxSeats'] = self.maxseats
|
||||
hh['seats'] = len(sqlids)
|
||||
# Flop turn and river may all be empty - add (likely) too many elements and trim with range
|
||||
boardcards = self.board['FLOP'] + self.board['TURN'] + self.board['RIVER'] + [u'0x', u'0x', u'0x', u'0x', u'0x']
|
||||
cards = [Card.encodeCard(c) for c in boardcards[0:5]]
|
||||
hh['boardcard1'] = cards[0]
|
||||
hh['boardcard2'] = cards[1]
|
||||
hh['boardcard3'] = cards[2]
|
||||
hh['boardcard4'] = cards[3]
|
||||
hh['boardcard5'] = cards[4]
|
||||
|
||||
# texture smallint,
|
||||
# playersVpi SMALLINT NOT NULL, /* num of players vpi */
|
||||
# Needs to be recorded
|
||||
# playersAtStreet1 SMALLINT NOT NULL, /* num of players seeing flop/street4 */
|
||||
# Needs to be recorded
|
||||
# playersAtStreet2 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtStreet3 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtStreet4 SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# playersAtShowdown SMALLINT NOT NULL,
|
||||
# Needs to be recorded
|
||||
# street0Raises TINYINT NOT NULL, /* num small bets paid to see flop/street4, including blind */
|
||||
# Needs to be recorded
|
||||
# street1Raises TINYINT NOT NULL, /* num small bets paid to see turn/street5 */
|
||||
# Needs to be recorded
|
||||
# street2Raises TINYINT NOT NULL, /* num big bets paid to see river/street6 */
|
||||
# Needs to be recorded
|
||||
# street3Raises TINYINT NOT NULL, /* num big bets paid to see sd/street7 */
|
||||
# Needs to be recorded
|
||||
# street4Raises TINYINT NOT NULL, /* num big bets paid to see showdown */
|
||||
# Needs to be recorded
|
||||
|
||||
#print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % self.getStreetTotals()
|
||||
#FIXME: Pot size still in decimal, needs to be converted to cents
|
||||
(hh['street1Pot'], hh['street2Pot'], hh['street3Pot'], hh['street4Pot'], hh['showdownPot']) = self.getStreetTotals()
|
||||
|
||||
# comment TEXT,
|
||||
# commentTs DATETIME
|
||||
#print hh
|
||||
handid = db.storeHand(hh)
|
||||
# HandsPlayers - ? ... Do we fix winnings?
|
||||
# Tourneys ?
|
||||
# TourneysPlayers
|
||||
|
||||
pass
|
||||
|
||||
def select(self, tourneyId):
|
||||
""" Function to create Tourney object from database """
|
||||
|
||||
|
||||
|
||||
def addPlayer(self, rank, name, winnings, payinAmount, nbRebuys, nbAddons, nbKO):
|
||||
def addPlayer(self, rank, name, winnings, winningsCurrency, rebuyCount, addOnCount, koCount):
|
||||
"""\
|
||||
Adds a player to the tourney, and initialises data structures indexed by player.
|
||||
rank (int) indicating the finishing rank (can be -1 if unknown)
|
||||
|
@ -266,13 +239,29 @@ winnings (decimal) the money the player ended the tourney with (can be 0, or
|
|||
"""
|
||||
log.debug("addPlayer: rank:%s - name : '%s' - Winnings (%s)" % (rank, name, winnings))
|
||||
self.players.append(name)
|
||||
self.finishPositions.update( { name : Decimal(rank) } )
|
||||
self.winnings.update( { name : Decimal(winnings) } )
|
||||
self.payinAmounts.update( {name : Decimal(payinAmount) } )
|
||||
self.countRebuys.update( {name: Decimal(nbRebuys) } )
|
||||
self.countAddOns.update( {name: Decimal(nbAddons) } )
|
||||
self.countKO.update( {name : Decimal(nbKO) } )
|
||||
if rank:
|
||||
self.ranks.update( { name : Decimal(rank) } )
|
||||
self.winnings.update( { name : Decimal(winnings) } )
|
||||
self.winningsCurrency.update( { name : winningsCurrency } )
|
||||
else:
|
||||
self.ranks.update( { name : None } )
|
||||
self.winnings.update( { name : None } )
|
||||
self.winningsCurrency.update( { name : None } )
|
||||
if rebuyCount:
|
||||
self.rebuyCounts.update( {name: Decimal(rebuyCount) } )
|
||||
else:
|
||||
self.rebuyCounts.update( {name: None } )
|
||||
|
||||
if addOnCount:
|
||||
self.addOnCounts.update( {name: Decimal(addOnCount) } )
|
||||
else:
|
||||
self.addOnCounts.update( {name: None } )
|
||||
|
||||
if koCount:
|
||||
self.koCounts.update( {name : Decimal(koCount) } )
|
||||
else:
|
||||
self.koCounts.update( {name: None } )
|
||||
#end def addPlayer
|
||||
|
||||
def incrementPlayerWinnings(self, name, additionnalWinnings):
|
||||
log.debug("incrementPlayerWinnings: name : '%s' - Add Winnings (%s)" % (name, additionnalWinnings))
|
||||
|
@ -289,35 +278,6 @@ winnings (decimal) the money the player ended the tourney with (can be 0, or
|
|||
print "checkPlayerExists", player, "fail"
|
||||
raise FpdbParseError
|
||||
|
||||
|
||||
def getGameTypeAsString(self):
|
||||
"""\
|
||||
Map the tuple self.gametype onto the pokerstars string describing it
|
||||
"""
|
||||
# currently it appears to be something like ["ring", "hold", "nl", sb, bb]:
|
||||
gs = {"holdem" : "Hold'em",
|
||||
"omahahi" : "Omaha",
|
||||
"omahahilo" : "Omaha Hi/Lo",
|
||||
"razz" : "Razz",
|
||||
"studhi" : "7 Card Stud",
|
||||
"studhilo" : "7 Card Stud Hi/Lo",
|
||||
"fivedraw" : "5 Card Draw",
|
||||
"27_1draw" : "FIXME",
|
||||
"27_3draw" : "Triple Draw 2-7 Lowball",
|
||||
"badugi" : "Badugi"
|
||||
}
|
||||
ls = {"nl" : "No Limit",
|
||||
"pl" : "Pot Limit",
|
||||
"fl" : "Limit",
|
||||
"cn" : "Cap No Limit",
|
||||
"cp" : "Cap Pot Limit"
|
||||
}
|
||||
|
||||
log.debug("gametype: %s" %(self.gametype))
|
||||
retstring = "%s %s" %(gs[self.gametype['category']], ls[self.gametype['limitType']])
|
||||
return retstring
|
||||
|
||||
|
||||
def writeSummary(self, fh=sys.__stdout__):
|
||||
print >>fh, "Override me"
|
||||
|
||||
|
@ -325,11 +285,11 @@ Map the tuple self.gametype onto the pokerstars string describing it
|
|||
self.writeSummary(sys.stdout)
|
||||
|
||||
|
||||
def assemble(cnxn, tourneyId):
|
||||
def assemble(cnxn, tourneyId): #TODO: move this method to Hand or Database
|
||||
# TODO !!
|
||||
c = cnxn.cursor()
|
||||
|
||||
# We need at least sitename, gametype, handid
|
||||
# We need at least siteName, gametype, handid
|
||||
# for the Hand.__init__
|
||||
c.execute("""
|
||||
select
|
||||
|
@ -365,7 +325,7 @@ limit 1""", {'handid':handid})
|
|||
#TODO: siteid should be in hands table - we took the scenic route through players here.
|
||||
res = c.fetchone()
|
||||
gametype = {'category':res[1],'base':res[2],'type':res[3],'limitType':res[4],'hilo':res[5],'sb':res[6],'bb':res[7], 'currency':res[10]}
|
||||
h = HoldemOmahaHand(hhc = None, sitename=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
|
||||
h = HoldemOmahaHand(hhc = None, siteName=res[0], gametype = gametype, handText=None, builtFrom = "DB", handid=handid)
|
||||
cards = map(Card.valueSuitFromCard, res[11:16] )
|
||||
if cards[0]:
|
||||
h.setCommunityCards('FLOP', cards[0:3])
|
||||
|
@ -383,15 +343,15 @@ limit 1""", {'handid':handid})
|
|||
SELECT
|
||||
h.sitehandno as hid,
|
||||
h.tablename as table,
|
||||
h.handstart as starttime
|
||||
h.startTime as startTime
|
||||
FROM
|
||||
hands as h
|
||||
Hands as h
|
||||
WHERE h.id = %(handid)s
|
||||
""", {'handid':handid})
|
||||
res = c.fetchone()
|
||||
h.handid = res[0]
|
||||
h.tablename = res[1]
|
||||
h.starttime = res[2] # automatically a datetime
|
||||
h.startTime = res[2] # automatically a datetime
|
||||
|
||||
# PlayerStacks
|
||||
c.execute("""
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010 Carl Gherardi
|
||||
|
@ -128,7 +128,7 @@ follow : whether to tail -f the input"""
|
|||
#2008/09/07 06:23:14 ET
|
||||
m2 = re.search("(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)", info[key])
|
||||
datetime = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), m2.group('M'),m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
|
||||
hand.starttime = time.strptime(datetime, "%Y/%m/%d %H:%M:%S")
|
||||
hand.startTime = time.strptime(datetime, "%Y/%m/%d %H:%M:%S")
|
||||
if key == 'HID':
|
||||
hand.handid = info[key]
|
||||
if key == 'TABLE':
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2008-2010, Carl Gherardi
|
||||
|
@ -137,7 +137,7 @@ class Win2day(HandHistoryConverter):
|
|||
for key in info:
|
||||
if key == 'DATETIME':
|
||||
# Win2day uses UTC timestamp
|
||||
hand.starttime = datetime.datetime.fromtimestamp(int(info[key]))
|
||||
hand.startTime = datetime.datetime.fromtimestamp(int(info[key]))
|
||||
if key == 'HID':
|
||||
hand.handid = info[key]
|
||||
if key == 'TABLE':
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""WinTables.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Discover_Tables.py
|
||||
|
||||
|
|
350
pyfpdb/fpdb.pyw
350
pyfpdb/fpdb.pyw
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -35,7 +35,10 @@ if os.name == 'nt' and sys.version[0:3] not in ('2.5', '2.6') and '-r' not in sy
|
|||
os.environ['PATH'] = tmppath
|
||||
print "Python " + sys.version[0:3] + ' - press return to continue\n'
|
||||
sys.stdin.readline()
|
||||
os.execvpe('pythonw.exe', ('pythonw.exe', 'fpdb.pyw', '-r'), os.environ) # first arg is ignored (name of program being run)
|
||||
if os.name=='nt':
|
||||
os.execvpe('pythonw.exe', ('pythonw.exe', 'fpdb.pyw', '-r'), os.environ) # first arg is ignored (name of program being run)
|
||||
else:
|
||||
os.execvpe('python', ('python', 'fpdb.pyw', '-r'), os.environ) # first arg is ignored (name of program being run)
|
||||
else:
|
||||
print "\npython 2.5 not found, please install python 2.5 or 2.6 for fpdb\n"
|
||||
raw_input("Press ENTER to continue.")
|
||||
|
@ -63,6 +66,7 @@ cl_options = string.join(sys.argv[1:])
|
|||
(options, argv) = Options.fpdb_options()
|
||||
|
||||
import logging, logging.config
|
||||
log = logging.getLogger("fpdb")
|
||||
|
||||
try:
|
||||
import pygtk
|
||||
|
@ -99,7 +103,9 @@ import GuiPrefs
|
|||
import GuiLogView
|
||||
import GuiDatabase
|
||||
import GuiBulkImport
|
||||
import GuiPlayerStats
|
||||
import ImapFetcher
|
||||
import GuiRingPlayerStats
|
||||
import GuiTourneyPlayerStats
|
||||
import GuiPositionalStats
|
||||
import GuiTableViewer
|
||||
import GuiAutoImport
|
||||
|
@ -109,8 +115,9 @@ import SQL
|
|||
import Database
|
||||
import Configuration
|
||||
import Exceptions
|
||||
import Stats
|
||||
|
||||
VERSION = "0.20"
|
||||
VERSION = "0.20.1 plus git"
|
||||
|
||||
|
||||
class fpdb:
|
||||
|
@ -230,6 +237,7 @@ class fpdb:
|
|||
dia.set_comments("")
|
||||
dia.set_license("This program is licensed under the AGPL3, see agpl-3.0.txt in the fpdb installation directory")
|
||||
dia.set_website("http://fpdb.sourceforge.net/")
|
||||
|
||||
dia.set_authors(['Steffen', 'Eratosthenes', 'Carl Gherardi',
|
||||
'Eric Blade', '_mt', 'sqlcoder', 'Bostik', 'and others'])
|
||||
dia.set_program_name("Free Poker Database (FPDB)")
|
||||
|
@ -256,11 +264,17 @@ class fpdb:
|
|||
view.modify_font(pango.FontDescription('monospace 10'))
|
||||
view.show()
|
||||
dia.vbox.pack_end(view, True, True, 2)
|
||||
l = gtk.Label('Version Information:')
|
||||
|
||||
l = gtk.Label("Your config file is: "+self.config.file)
|
||||
l.set_alignment(0.5, 0.5)
|
||||
l.show()
|
||||
dia.vbox.pack_end(l, True, True, 2)
|
||||
|
||||
l = gtk.Label('Version Information:')
|
||||
l.set_alignment(0.5, 0.5)
|
||||
l.show()
|
||||
dia.vbox.pack_end(l, True, True, 2)
|
||||
|
||||
dia.run()
|
||||
dia.destroy()
|
||||
log.debug("Threads: ")
|
||||
|
@ -295,7 +309,7 @@ class fpdb:
|
|||
self.warning_box("Unimplemented: Maintain Databases")
|
||||
return
|
||||
if len(self.tab_names) == 1:
|
||||
if self.obtain_global_lock(): # returns true if successful
|
||||
if self.obtain_global_lock("dia_maintain_dbs"): # returns true if successful
|
||||
# only main tab has been opened, open dialog
|
||||
dia = gtk.Dialog("Maintain Databases",
|
||||
self.window,
|
||||
|
@ -317,59 +331,179 @@ class fpdb:
|
|||
self.warning_box("Cannot open Database Maintenance window because "
|
||||
+ "other windows have been opened. Re-start fpdb to use this option.")
|
||||
|
||||
def dia_create_del_user(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Create/Delete user")
|
||||
self.obtain_global_lock()
|
||||
self.release_global_lock()
|
||||
|
||||
def dia_database_stats(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Database Stats")
|
||||
self.warning_box(str="Number of Hands: "+str(self.db.getHandCount())+
|
||||
"\nNumber of Tourneys: "+str(self.db.getTourneyCount())+
|
||||
"\nNumber of TourneyTypes: "+str(self.db.getTourneyTypeCount()),
|
||||
diatitle="Database Statistics")
|
||||
#end def dia_database_stats
|
||||
|
||||
def dia_delete_db_parts(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Delete Database Parts")
|
||||
self.obtain_global_lock()
|
||||
self.release_global_lock()
|
||||
|
||||
def dia_edit_profile(self, widget=None, data=None, create_default=False, path=None):
|
||||
self.warning_box("Unimplemented: Edit Profile")
|
||||
self.obtain_global_lock()
|
||||
self.release_global_lock()
|
||||
|
||||
def dia_export_db(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Export Database")
|
||||
self.obtain_global_lock()
|
||||
self.release_global_lock()
|
||||
|
||||
def dia_get_db_root_credentials(self):
|
||||
"""obtains db root credentials from user"""
|
||||
self.warning_box("Unimplemented: Get Root Database Credentials")
|
||||
# user, pw=None, None
|
||||
#
|
||||
# dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0,
|
||||
# buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK))
|
||||
#
|
||||
# label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.")
|
||||
#
|
||||
#
|
||||
# label_user=gtk.Label("Username")
|
||||
# dialog.vbox.add(label_user)
|
||||
# label_user.show()
|
||||
#
|
||||
# response=dialog.run()
|
||||
# dialog.destroy()
|
||||
# return (user, pw, response)
|
||||
|
||||
def dia_import_db(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Import Database")
|
||||
self.obtain_global_lock()
|
||||
def diaHudConfigurator(self, widget, data=None):
|
||||
"""Opens dialog to set parameters (game category, row count, column count for HUD stat configurator"""
|
||||
self.hudConfiguratorRows=None
|
||||
self.hudConfiguratorColumns=None
|
||||
self.hudConfiguratorGame=None
|
||||
|
||||
diaSelections = gtk.Dialog("HUD Configurator - choose category",
|
||||
self.window,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
|
||||
|
||||
label=gtk.Label("Please select the game category for which you want to configure HUD stats:")
|
||||
diaSelections.vbox.add(label)
|
||||
label.show()
|
||||
|
||||
comboGame = gtk.combo_box_new_text()
|
||||
comboGame.connect("changed", self.hudConfiguratorComboSelection)
|
||||
diaSelections.vbox.add(comboGame)
|
||||
games=self.config.get_supported_games()
|
||||
for game in games:
|
||||
comboGame.append_text(game)
|
||||
comboGame.set_active(0)
|
||||
comboGame.show()
|
||||
|
||||
comboRows = gtk.combo_box_new_text()
|
||||
comboRows.connect("changed", self.hudConfiguratorComboSelection)
|
||||
diaSelections.vbox.add(comboRows)
|
||||
for i in range(1,8):
|
||||
comboRows.append_text(str(i)+" rows")
|
||||
comboRows.set_active(0)
|
||||
comboRows.show()
|
||||
|
||||
comboColumns = gtk.combo_box_new_text()
|
||||
comboColumns.connect("changed", self.hudConfiguratorComboSelection)
|
||||
diaSelections.vbox.add(comboColumns)
|
||||
for i in range(1,8):
|
||||
comboColumns.append_text(str(i)+" columns")
|
||||
comboColumns.set_active(0)
|
||||
comboColumns.show()
|
||||
|
||||
response=diaSelections.run()
|
||||
diaSelections.destroy()
|
||||
|
||||
if response == gtk.RESPONSE_ACCEPT and self.hudConfiguratorRows!=None and self.hudConfiguratorColumns!=None and self.hudConfiguratorGame!=None:
|
||||
#print "clicked ok and selected:", self.hudConfiguratorGame,"with", str(self.hudConfiguratorRows), "rows and", str(self.hudConfiguratorColumns), "columns"
|
||||
self.diaHudConfiguratorTable()
|
||||
#end def diaHudConfigurator
|
||||
|
||||
def hudConfiguratorComboSelection(self, widget):
|
||||
#TODO: remove this and handle it directly in diaHudConfigurator
|
||||
result=widget.get_active_text()
|
||||
if result.endswith(" rows"):
|
||||
self.hudConfiguratorRows=int(result[0])
|
||||
elif result.endswith(" columns"):
|
||||
self.hudConfiguratorColumns=int(result[0])
|
||||
else:
|
||||
self.hudConfiguratorGame=result
|
||||
#end def hudConfiguratorComboSelection
|
||||
|
||||
def diaHudConfiguratorTable(self):
|
||||
"""shows dialogue with Table of ComboBoxes to allow choosing of HUD stats"""
|
||||
#TODO: add notices to hud configurator: no duplicates, no empties, display options
|
||||
#TODO: show explanation of what each stat means
|
||||
diaHudTable = gtk.Dialog("HUD Configurator - please choose your stats",
|
||||
self.window,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT,
|
||||
gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
|
||||
|
||||
label=gtk.Label("Please choose the stats you wish to use in the below table.")
|
||||
diaHudTable.vbox.add(label)
|
||||
label.show()
|
||||
|
||||
label=gtk.Label("Note that you may not select any stat more than once or it will crash.")
|
||||
diaHudTable.vbox.add(label)
|
||||
label.show()
|
||||
|
||||
label=gtk.Label("It is not currently possible to select \"empty\" or anything else to that end.")
|
||||
diaHudTable.vbox.add(label)
|
||||
label.show()
|
||||
|
||||
label=gtk.Label("To configure things like colouring you will still have to manually edit your HUD_config.xml.")
|
||||
diaHudTable.vbox.add(label)
|
||||
label.show()
|
||||
|
||||
self.hudConfiguratorTableContents=[]
|
||||
table= gtk.Table(rows=self.hudConfiguratorRows+1, columns=self.hudConfiguratorColumns+1, homogeneous=True)
|
||||
|
||||
statDir=dir(Stats)
|
||||
statDict={}
|
||||
for attr in statDir:
|
||||
if attr.startswith('__'): continue
|
||||
if attr in ("Charset", "Configuration", "Database", "GInitiallyUnowned", "gtk", "pygtk",
|
||||
"player", "c", "db_connection", "do_stat", "do_tip", "stat_dict",
|
||||
"h", "re", "re_Percent", "re_Places", ): continue
|
||||
statDict[attr]=eval("Stats.%s.__doc__" % (attr))
|
||||
|
||||
for rowNumber in range(self.hudConfiguratorRows+1):
|
||||
newRow=[]
|
||||
for columnNumber in range(self.hudConfiguratorColumns+1):
|
||||
if rowNumber==0:
|
||||
if columnNumber==0:
|
||||
pass
|
||||
else:
|
||||
label=gtk.Label("column "+str(columnNumber))
|
||||
table.attach(child=label, left_attach=columnNumber, right_attach=columnNumber+1, top_attach=rowNumber, bottom_attach=rowNumber+1)
|
||||
label.show()
|
||||
elif columnNumber==0:
|
||||
label=gtk.Label("row "+str(rowNumber))
|
||||
table.attach(child=label, left_attach=columnNumber, right_attach=columnNumber+1, top_attach=rowNumber, bottom_attach=rowNumber+1)
|
||||
label.show()
|
||||
else:
|
||||
comboBox = gtk.combo_box_new_text()
|
||||
|
||||
for stat in statDict.keys():
|
||||
comboBox.append_text(stat)
|
||||
comboBox.set_active(0)
|
||||
|
||||
newRow.append(comboBox)
|
||||
table.attach(child=comboBox, left_attach=columnNumber, right_attach=columnNumber+1, top_attach=rowNumber, bottom_attach=rowNumber+1)
|
||||
|
||||
comboBox.show()
|
||||
if rowNumber!=0:
|
||||
self.hudConfiguratorTableContents.append(newRow)
|
||||
diaHudTable.vbox.add(table)
|
||||
table.show()
|
||||
|
||||
response=diaHudTable.run()
|
||||
diaHudTable.destroy()
|
||||
|
||||
if response == gtk.RESPONSE_ACCEPT:
|
||||
self.storeNewHudStatConfig()
|
||||
#end def diaHudConfiguratorTable
|
||||
|
||||
def storeNewHudStatConfig(self):
|
||||
"""stores selections made in diaHudConfiguratorTable"""
|
||||
self.obtain_global_lock("diaHudConfiguratorTable")
|
||||
statTable=[]
|
||||
for row in self.hudConfiguratorTableContents:
|
||||
newRow=[]
|
||||
for column in row:
|
||||
newField = column.get_active_text()
|
||||
newRow.append(newField)
|
||||
statTable.append(newRow)
|
||||
|
||||
self.config.editStats(self.hudConfiguratorGame,statTable)
|
||||
self.config.save() #TODO: make it not store in horrible formatting
|
||||
self.release_global_lock()
|
||||
#end def storeNewHudStatConfig
|
||||
|
||||
def dia_dump_db(self, widget, data=None):
|
||||
filename = "database-dump.sql"
|
||||
result = self.db.dumpDatabase()
|
||||
|
||||
dumpFile = open(filename, 'w')
|
||||
dumpFile.write(result)
|
||||
dumpFile.close()
|
||||
#end def dia_database_stats
|
||||
|
||||
def dia_licensing(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Licensing")
|
||||
|
||||
def dia_load_profile(self, widget, data=None):
|
||||
"""Dialogue to select a file to load a profile from"""
|
||||
if self.obtain_global_lock(): # returns true if successful
|
||||
if self.obtain_global_lock("fpdb.dia_load_profile"): # returns true if successful
|
||||
#try:
|
||||
# chooser = gtk.FileChooserDialog(title="Please select a profile file to load",
|
||||
# action=gtk.FILE_CHOOSER_ACTION_OPEN,
|
||||
|
@ -392,13 +526,13 @@ class fpdb:
|
|||
|
||||
def dia_recreate_tables(self, widget, data=None):
|
||||
"""Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
|
||||
if self.obtain_global_lock(): # returns true if successful
|
||||
if self.obtain_global_lock("fpdb.dia_recreate_tables"): # returns true if successful
|
||||
|
||||
#lock_released = False
|
||||
dia_confirm = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_WARNING,
|
||||
buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm deleting and recreating tables")
|
||||
diastring = "Please confirm that you want to (re-)create the tables. If there already are tables in the database " \
|
||||
+self.db.database+" on "+self.db.host+" they will be deleted."
|
||||
+self.db.database+" on "+self.db.host+" they will be deleted.\nThis may take a while."
|
||||
dia_confirm.format_secondary_text(diastring)#todo: make above string with bold for db, host and deleted
|
||||
# disable windowclose, do not want the the underlying processing interrupted mid-process
|
||||
dia_confirm.set_deletable(False)
|
||||
|
@ -413,24 +547,31 @@ class fpdb:
|
|||
# self.release_global_lock()
|
||||
# lock_released = True
|
||||
self.db.recreate_tables()
|
||||
# find any guibulkimport windows and clear player cache:
|
||||
for t in self.threads:
|
||||
if isinstance(t, GuiBulkImport.GuiBulkImport):
|
||||
t.importer.database.resetPlayerIDs()
|
||||
self.release_global_lock()
|
||||
#else:
|
||||
# for other dbs use same connection as holds global lock
|
||||
# self.fdb_lock.fdb.recreate_tables()
|
||||
# TODO: figure out why this seems to be necessary
|
||||
dia_restart = gtk.MessageDialog(parent=self.window, flags=0, type=gtk.MESSAGE_WARNING,
|
||||
buttons=(gtk.BUTTONS_OK), message_format="Restart fpdb")
|
||||
diastring = "You should now restart fpdb."
|
||||
diastring = "Fpdb now needs to close. Please restart it."
|
||||
dia_restart.format_secondary_text(diastring)
|
||||
|
||||
dia_restart.run()
|
||||
dia_restart.destroy()
|
||||
self.quit(None, None)
|
||||
elif response == gtk.RESPONSE_NO:
|
||||
self.release_global_lock()
|
||||
print 'User cancelled recreating tables'
|
||||
#if not lock_released:
|
||||
self.release_global_lock()
|
||||
#end def dia_recreate_tables
|
||||
|
||||
def dia_recreate_hudcache(self, widget, data=None):
|
||||
if self.obtain_global_lock():
|
||||
if self.obtain_global_lock("dia_recreate_hudcache"):
|
||||
self.dia_confirm = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm recreating HUD cache")
|
||||
diastring = "Please confirm that you want to re-create the HUD cache."
|
||||
self.dia_confirm.format_secondary_text(diastring)
|
||||
|
@ -482,7 +623,7 @@ class fpdb:
|
|||
self.release_global_lock()
|
||||
|
||||
def dia_rebuild_indexes(self, widget, data=None):
|
||||
if self.obtain_global_lock():
|
||||
if self.obtain_global_lock("dia_rebuild_indexes"):
|
||||
self.dia_confirm = gtk.MessageDialog(parent=self.window
|
||||
,flags=gtk.DIALOG_DESTROY_WITH_PARENT
|
||||
,type=gtk.MESSAGE_WARNING
|
||||
|
@ -523,7 +664,7 @@ class fpdb:
|
|||
"""opens the log viewer window"""
|
||||
|
||||
#lock_set = False
|
||||
#if self.obtain_global_lock():
|
||||
#if self.obtain_global_lock("dia_logs"):
|
||||
# lock_set = True
|
||||
|
||||
# remove members from self.threads if close messages received
|
||||
|
@ -609,8 +750,8 @@ class fpdb:
|
|||
|
||||
def dia_regression_test(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Regression Test")
|
||||
self.obtain_global_lock()
|
||||
self.release_global_lock()
|
||||
#self.obtain_global_lock("dia_regression_test")
|
||||
#self.release_global_lock()
|
||||
|
||||
def dia_save_profile(self, widget, data=None):
|
||||
self.warning_box("Unimplemented: Save Profile (try saving a HUD layout, that should do it)")
|
||||
|
@ -640,8 +781,8 @@ class fpdb:
|
|||
<menubar name="MenuBar">
|
||||
<menu action="main">
|
||||
<menuitem action="LoadProf"/>
|
||||
<menuitem action="EditProf"/>
|
||||
<menuitem action="SaveProf"/>
|
||||
<menuitem action="hudConfigurator"/>
|
||||
<menuitem action="Preferences"/>
|
||||
<separator/>
|
||||
<menuitem action="Quit"/>
|
||||
|
@ -649,30 +790,28 @@ class fpdb:
|
|||
<menu action="import">
|
||||
<menuitem action="sethharchive"/>
|
||||
<menuitem action="bulkimp"/>
|
||||
<menuitem action="imapsummaries"/>
|
||||
<menuitem action="autoimp"/>
|
||||
<menuitem action="autorate"/>
|
||||
</menu>
|
||||
<menu action="viewers">
|
||||
<menuitem action="autoimp"/>
|
||||
<menuitem action="hudConfigurator"/>
|
||||
<menuitem action="graphs"/>
|
||||
<menuitem action="handreplay"/>
|
||||
<menuitem action="playerdetails"/>
|
||||
<menuitem action="playerstats"/>
|
||||
<menuitem action="ringplayerstats"/>
|
||||
<menuitem action="tourneyplayerstats"/>
|
||||
<menuitem action="posnstats"/>
|
||||
<menuitem action="sessionstats"/>
|
||||
<menuitem action="sessionreplay"/>
|
||||
<menuitem action="tableviewer"/>
|
||||
</menu>
|
||||
<menu action="database">
|
||||
<menuitem action="maintaindbs"/>
|
||||
<menuitem action="createuser"/>
|
||||
<menuitem action="createtabs"/>
|
||||
<menuitem action="rebuildhudcache"/>
|
||||
<menuitem action="rebuildindexes"/>
|
||||
<menuitem action="stats"/>
|
||||
<menuitem action="databasestats"/>
|
||||
<menuitem action="dumptofile"/>
|
||||
</menu>
|
||||
<menu action="help">
|
||||
<menuitem action="Abbrev"/>
|
||||
<menuitem action="Logs"/>
|
||||
<separator/>
|
||||
<menuitem action="About"/>
|
||||
|
@ -689,32 +828,29 @@ class fpdb:
|
|||
actiongroup.add_actions([('main', None, '_Main'),
|
||||
('Quit', gtk.STOCK_QUIT, '_Quit', None, 'Quit the Program', self.quit),
|
||||
('LoadProf', None, '_Load Profile (broken)', '<control>L', 'Load your profile', self.dia_load_profile),
|
||||
('EditProf', None, '_Edit Profile (todo)', '<control>E', 'Edit your profile', self.dia_edit_profile),
|
||||
('SaveProf', None, '_Save Profile (todo)', '<control>S', 'Save your profile', self.dia_save_profile),
|
||||
('Preferences', None, 'Pre_ferences', '<control>F', 'Edit your preferences', self.dia_preferences),
|
||||
('import', None, '_Import'),
|
||||
('sethharchive', None, '_Set HandHistory Archive Directory', None, 'Set HandHistory Archive Directory', self.select_hhArchiveBase),
|
||||
('bulkimp', None, '_Bulk Import', '<control>B', 'Bulk Import', self.tab_bulk_import),
|
||||
('autorate', None, 'Auto _Rating (todo)', '<control>R', 'Auto Rating (todo)', self.not_implemented),
|
||||
('imapsummaries', None, '_Import Tourney Summaries through eMail/IMAP', '<control>I', 'Auto Import and HUD', self.import_imap_summaries),
|
||||
('viewers', None, '_Viewers'),
|
||||
('autoimp', None, '_Auto Import and HUD', '<control>A', 'Auto Import and HUD', self.tab_auto_import),
|
||||
('hudConfigurator', None, '_HUD Configurator', '<control>H', 'HUD Configurator', self.diaHudConfigurator),
|
||||
('graphs', None, '_Graphs', '<control>G', 'Graphs', self.tabGraphViewer),
|
||||
('handreplay', None, 'Hand _Replayer (todo)', None, 'Hand Replayer (todo)', self.not_implemented),
|
||||
('playerdetails', None, 'Player _Details (todo)', None, 'Player Details (todo)', self.not_implemented),
|
||||
('playerstats', None, '_Player Stats (tabulated view)', '<control>P', 'Player Stats (tabulated view)', self.tab_player_stats),
|
||||
('ringplayerstats', None, 'Ring _Player Stats (tabulated view)', '<control>P', 'Ring Player Stats (tabulated view)', self.tab_ring_player_stats),
|
||||
('tourneyplayerstats', None, '_Tourney Player Stats (tabulated view)', '<control>T', 'Tourney Player Stats (tabulated view)', self.tab_tourney_player_stats),
|
||||
('posnstats', None, 'P_ositional Stats (tabulated view)', '<control>O', 'Positional Stats (tabulated view)', self.tab_positional_stats),
|
||||
('sessionstats', None, 'Session Stats', None, 'Session Stats', self.tab_session_stats),
|
||||
('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented),
|
||||
('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer),
|
||||
('database', None, '_Database'),
|
||||
('maintaindbs', None, '_Maintain Databases (todo)', None, 'Maintain Databases', self.dia_maintain_dbs),
|
||||
('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user),
|
||||
('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables),
|
||||
('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache),
|
||||
('rebuildindexes', None, 'Rebuild DB Indexes', None, 'Rebuild DB Indexes', self.dia_rebuild_indexes),
|
||||
('stats', None, '_Statistics (todo)', None, 'View Database Statistics', self.dia_database_stats),
|
||||
('databasestats', None, '_Statistics', None, 'View Database Statistics', self.dia_database_stats),
|
||||
('dumptofile', None, 'Dump Database to Textfile (takes ALOT of time)', None, 'Dump Database to Textfile (takes ALOT of time)', self.dia_dump_db),
|
||||
('help', None, '_Help'),
|
||||
('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations),
|
||||
('Logs', None, '_Log Messages', None, 'Log and Debug Messages', self.dia_logs),
|
||||
('About', None, 'A_bout', None, 'About the program', self.dia_about),
|
||||
('License', None, '_License and Copying (todo)', None, 'License and Copying', self.dia_licensing),
|
||||
|
@ -728,6 +864,12 @@ class fpdb:
|
|||
menubar = uimanager.get_widget('/MenuBar')
|
||||
window.add_accel_group(accel_group)
|
||||
return menubar
|
||||
#end def get_menu
|
||||
|
||||
def import_imap_summaries(self, widget, data=None):
|
||||
result=ImapFetcher.run(self.config, self.db)
|
||||
#print "import imap summaries result:", result
|
||||
#end def import_imap_summaries
|
||||
|
||||
def load_profile(self, create_db = False):
|
||||
"""Loads profile from the provided path name."""
|
||||
|
@ -832,12 +974,13 @@ class fpdb:
|
|||
def not_implemented(self, widget, data=None):
|
||||
self.warning_box("Unimplemented menu entry")
|
||||
|
||||
def obtain_global_lock(self):
|
||||
ret = self.lock.acquire(False) # will return false if lock is already held
|
||||
def obtain_global_lock(self, source):
|
||||
ret = self.lock.acquire(source=source) # will return false if lock is already held
|
||||
if ret:
|
||||
print "\nGlobal lock taken ..."
|
||||
print "\nGlobal lock taken by", source
|
||||
self.lockTakenBy=source
|
||||
else:
|
||||
print "\nFailed to get global lock."
|
||||
print "\nFailed to get global lock, it is currently held by", source
|
||||
return ret
|
||||
# need to release it later:
|
||||
# self.lock.release()
|
||||
|
@ -845,12 +988,23 @@ class fpdb:
|
|||
def quit(self, widget, data=None):
|
||||
# TODO: can we get some / all of the stuff done in this function to execute on any kind of abort?
|
||||
#FIXME get two "quitting normally" messages, following the addition of the self.window.destroy() call
|
||||
print "Quitting normally"
|
||||
# ... because self.window.destroy() leads to self.destroy() which calls this!
|
||||
if not self.quitting:
|
||||
print "Quitting normally"
|
||||
self.quitting = True
|
||||
# TODO: check if current settings differ from profile, if so offer to save or abort
|
||||
try:
|
||||
if self.db is not None and self.db.connected:
|
||||
self.db.disconnect()
|
||||
except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected
|
||||
|
||||
if self.db!=None:
|
||||
if self.db.backend==self.db.MYSQL_INNODB:
|
||||
try:
|
||||
if self.db is not None and self.db.connected():
|
||||
self.db.disconnect()
|
||||
except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected
|
||||
pass
|
||||
else:
|
||||
if self.db is not None and self.db.connected():
|
||||
self.db.disconnect()
|
||||
else:
|
||||
pass
|
||||
self.statusIcon.set_visible(False)
|
||||
|
||||
|
@ -859,11 +1013,9 @@ class fpdb:
|
|||
|
||||
def release_global_lock(self):
|
||||
self.lock.release()
|
||||
self.lockTakenBy=None
|
||||
print "Global lock released.\n"
|
||||
|
||||
def tab_abbreviations(self, widget, data=None):
|
||||
print "todo: implement tab_abbreviations"
|
||||
|
||||
def tab_auto_import(self, widget, data=None):
|
||||
"""opens the auto import tab"""
|
||||
new_aimp_thread = GuiAutoImport.GuiAutoImport(self.settings, self.config, self.sql, self.window)
|
||||
|
@ -873,17 +1025,22 @@ class fpdb:
|
|||
|
||||
def tab_bulk_import(self, widget, data=None):
|
||||
"""opens a tab for bulk importing"""
|
||||
#print "start of tab_bulk_import"
|
||||
new_import_thread = GuiBulkImport.GuiBulkImport(self.settings, self.config, self.sql)
|
||||
self.threads.append(new_import_thread)
|
||||
bulk_tab=new_import_thread.get_vbox()
|
||||
self.add_and_display_tab(bulk_tab, "Bulk Import")
|
||||
|
||||
def tab_player_stats(self, widget, data=None):
|
||||
new_ps_thread = GuiPlayerStats.GuiPlayerStats(self.config, self.sql, self.window)
|
||||
def tab_ring_player_stats(self, widget, data=None):
|
||||
new_ps_thread = GuiRingPlayerStats.GuiRingPlayerStats(self.config, self.sql, self.window)
|
||||
self.threads.append(new_ps_thread)
|
||||
ps_tab=new_ps_thread.get_vbox()
|
||||
self.add_and_display_tab(ps_tab, "Player Stats")
|
||||
self.add_and_display_tab(ps_tab, "Ring Player Stats")
|
||||
|
||||
def tab_tourney_player_stats(self, widget, data=None):
|
||||
new_ps_thread = GuiTourneyPlayerStats.GuiTourneyPlayerStats(self.config, self.db, self.sql, self.window)
|
||||
self.threads.append(new_ps_thread)
|
||||
ps_tab=new_ps_thread.get_vbox()
|
||||
self.add_and_display_tab(ps_tab, "Tourney Player Stats")
|
||||
|
||||
def tab_positional_stats(self, widget, data=None):
|
||||
new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql)
|
||||
|
@ -900,8 +1057,12 @@ class fpdb:
|
|||
def tab_main_help(self, widget, data=None):
|
||||
"""Displays a tab with the main fpdb help screen"""
|
||||
mh_tab=gtk.Label("""Welcome to Fpdb!
|
||||
To be notified of new snapshots and releases go to https://lists.sourceforge.net/lists/listinfo/fpdb-announce and subscribe.
|
||||
If you want to follow development more closely go to https://lists.sourceforge.net/lists/listinfo/fpdb-main and subscribe.
|
||||
|
||||
This program is currently in an alpha-state, so our database format is still sometimes changed.
|
||||
You should therefore always keep your hand history files so that you can re-import after an update, if necessary.
|
||||
|
||||
For documentation please visit our website at http://fpdb.sourceforge.net/.
|
||||
If you need help click on Contact - Get Help on our website.
|
||||
Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml.
|
||||
|
@ -929,11 +1090,12 @@ You can find the full license texts in agpl-3.0.txt, gpl-2.0.txt and gpl-3.0.txt
|
|||
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
|
||||
self.db = None
|
||||
self.status_bar = None
|
||||
self.quitting = False
|
||||
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
self.window.connect("delete_event", self.delete_event)
|
||||
self.window.connect("destroy", self.destroy)
|
||||
self.window.set_title("Free Poker DB - v%s or higher" % (VERSION, ))
|
||||
self.window.set_title("Free Poker DB - v%s" % (VERSION, ))
|
||||
self.window.set_border_width(1)
|
||||
defx, defy = 900, 720
|
||||
sx, sy = gtk.gdk.screen_width(), gtk.gdk.screen_height()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
@ -152,6 +152,8 @@ class Importer:
|
|||
#Add an individual file to filelist
|
||||
def addImportFile(self, filename, site = "default", filter = "passthrough"):
|
||||
#TODO: test it is a valid file -> put that in config!!
|
||||
#print "addimportfile: filename is a", filename.__class__
|
||||
# filename now comes in as unicode
|
||||
if filename in self.filelist or not os.path.exists(filename):
|
||||
return
|
||||
self.filelist[filename] = [site] + [filter]
|
||||
|
@ -177,10 +179,11 @@ class Importer:
|
|||
if os.path.isdir(inputPath):
|
||||
for subdir in os.walk(inputPath):
|
||||
for file in subdir[2]:
|
||||
self.addImportFile(os.path.join(subdir[0], file), site=site,
|
||||
filter=filter)
|
||||
self.addImportFile(unicode(os.path.join(subdir[0], file),'utf-8'),
|
||||
site=site, filter=filter)
|
||||
else:
|
||||
self.addImportFile(inputPath, site=site, filter=filter)
|
||||
|
||||
self.addImportFile(unicode(inputPath,'utf-8'), site=site, filter=filter)
|
||||
#Add a directory of files to filelist
|
||||
#Only one import directory per site supported.
|
||||
#dirlist is a hash of lists:
|
||||
|
@ -406,7 +409,8 @@ class Importer:
|
|||
conv = None
|
||||
(stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, time())
|
||||
|
||||
file = file.decode(Configuration.LOCALE_ENCODING)
|
||||
# sc: is there any need to decode this? maybe easier to skip it than guess at the encoding?
|
||||
#file = file.decode("utf-8") #(Configuration.LOCALE_ENCODING)
|
||||
|
||||
# Load filter, process file, pass returned filename to import_fpdb_file
|
||||
if self.settings['threads'] > 0 and self.writeq is not None:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Code from http://ender.snowburst.org:4747/~jjohns/interlocks.py
|
||||
# Thanks JJ!
|
||||
|
@ -34,19 +35,24 @@ class InterProcessLockBase:
|
|||
if not name:
|
||||
name = sys.argv[0]
|
||||
self.name = name
|
||||
self.heldBy = None
|
||||
|
||||
def getHashedName(self):
|
||||
return base64.b64encode(self.name).replace('=','')
|
||||
|
||||
def acquire_impl(self, wait): abstract
|
||||
|
||||
def acquire(self, wait=False, retry_time=1):
|
||||
def acquire(self, wait=False, retry_time=1, source=None):
|
||||
if source == None:
|
||||
source="Unknown"
|
||||
if self._has_lock: # make sure 2nd acquire in same process fails
|
||||
print "lock already held by:",self.heldBy
|
||||
return False
|
||||
while not self._has_lock:
|
||||
try:
|
||||
self.acquire_impl(wait)
|
||||
self._has_lock = True
|
||||
self.heldBy=source
|
||||
#print 'i have the lock'
|
||||
except SingleInstanceError:
|
||||
if not wait:
|
||||
|
@ -58,6 +64,7 @@ class InterProcessLockBase:
|
|||
def release(self):
|
||||
self.release_impl()
|
||||
self._has_lock = False
|
||||
self.heldBy=None
|
||||
|
||||
def locked(self):
|
||||
if self.acquire():
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Eric Blade
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""setup.py
|
||||
|
|
116
pyfpdb/regression-test-files/results/0001_empty_DB.txt
Normal file
116
pyfpdb/regression-test-files/results/0001_empty_DB.txt
Normal file
|
@ -0,0 +1,116 @@
|
|||
fpdb database dump
|
||||
DB version=136
|
||||
|
||||
###################
|
||||
Table Autorates
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table Backings
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table Gametypes
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table Hands
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table HandsActions
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table HandsPlayers
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table HudCache
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table Players
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table Settings
|
||||
###################
|
||||
version=136
|
||||
|
||||
|
||||
###################
|
||||
Table Sites
|
||||
###################
|
||||
id=1
|
||||
name=Full Tilt Poker
|
||||
code=FT
|
||||
|
||||
id=2
|
||||
name=PokerStars
|
||||
code=PS
|
||||
|
||||
id=3
|
||||
name=Everleaf
|
||||
code=EV
|
||||
|
||||
id=4
|
||||
name=Win2day
|
||||
code=W2
|
||||
|
||||
id=5
|
||||
name=OnGame
|
||||
code=OG
|
||||
|
||||
id=6
|
||||
name=UltimateBet
|
||||
code=UB
|
||||
|
||||
id=7
|
||||
name=Betfair
|
||||
code=BF
|
||||
|
||||
id=8
|
||||
name=Absolute
|
||||
code=AB
|
||||
|
||||
id=9
|
||||
name=PartyPoker
|
||||
code=PP
|
||||
|
||||
id=10
|
||||
name=Partouche
|
||||
code=PA
|
||||
|
||||
id=11
|
||||
name=Carbon
|
||||
code=CA
|
||||
|
||||
id=12
|
||||
name=PKR
|
||||
code=PK
|
||||
|
||||
|
||||
###################
|
||||
Table TourneyTypes
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table Tourneys
|
||||
###################
|
||||
empty table
|
||||
|
||||
###################
|
||||
Table TourneysPlayers
|
||||
###################
|
||||
empty table
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Carl Gherardi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Matt Turnbull
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Matt Turnbull
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Matt Turnbull
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Carl Gherardi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Steffen Schaumburg
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2008-2010 Carl Gherardi
|
||||
|
|
14
setup.py
14
setup.py
|
@ -1,19 +1,11 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Mika Bostrom
|
||||
#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.
|
||||
#Created by Mika Bostrom, released into the public domain as far as legally possible.
|
||||
#
|
||||
#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.
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
# Python packaging for fpdb
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""test1.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""test2.py
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#Copyright 2009-2010 Ray E. Barker
|
||||
|
|
Loading…
Reference in New Issue
Block a user