diff --git a/packaging/gentoo/fpdb-0.20-r1.ebuild b/packaging/gentoo/fpdb-0.20-r1.ebuild new file mode 100644 index 00000000..988a90b1 --- /dev/null +++ b/packaging/gentoo/fpdb-0.20-r1.ebuild @@ -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." +} diff --git a/packaging/gentoo/fpdb-0.20-r2.ebuild b/packaging/gentoo/fpdb-0.20-r2.ebuild new file mode 100644 index 00000000..2e04719e --- /dev/null +++ b/packaging/gentoo/fpdb-0.20-r2.ebuild @@ -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." +} diff --git a/packaging/gentoo/fpdb-0.20.1.ebuild b/packaging/gentoo/fpdb-0.20.1.ebuild new file mode 100644 index 00000000..a99e1bfe --- /dev/null +++ b/packaging/gentoo/fpdb-0.20.1.ebuild @@ -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 +} diff --git a/packaging/gentoo/fpdb-0.20_rc1.ebuild b/packaging/gentoo/fpdb-0.20_rc1.ebuild deleted file mode 100644 index cfff9fac..00000000 --- a/packaging/gentoo/fpdb-0.20_rc1.ebuild +++ /dev/null @@ -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!" -} diff --git a/pyfpdb/AbsoluteToFpdb.py b/pyfpdb/AbsoluteToFpdb.py index 03266bc8..26aea7e8 100755 --- a/pyfpdb/AbsoluteToFpdb.py +++ b/pyfpdb/AbsoluteToFpdb.py @@ -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): diff --git a/pyfpdb/AlchemyFacilities.py b/pyfpdb/AlchemyFacilities.py index dc5b57af..157d68f2 100644 --- a/pyfpdb/AlchemyFacilities.py +++ b/pyfpdb/AlchemyFacilities.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- #Copyright 2009-2010 Grigorij Indigirkin diff --git a/pyfpdb/AlchemyMappings.py b/pyfpdb/AlchemyMappings.py index 0330c656..15cdf70d 100644 --- a/pyfpdb/AlchemyMappings.py +++ b/pyfpdb/AlchemyMappings.py @@ -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""" diff --git a/pyfpdb/AlchemyTables.py b/pyfpdb/AlchemyTables.py index b751fd82..84c4b9f0 100644 --- a/pyfpdb/AlchemyTables.py +++ b/pyfpdb/AlchemyTables.py @@ -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', diff --git a/pyfpdb/Anonymise.py b/pyfpdb/Anonymise.py index 4922ef86..6b7e7352 100755 --- a/pyfpdb/Anonymise.py +++ b/pyfpdb/Anonymise.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- #Copyright 2009-2010 Carl Gherardi diff --git a/pyfpdb/BetfairToFpdb.py b/pyfpdb/BetfairToFpdb.py index 80fdfb11..5305c1c1 100755 --- a/pyfpdb/BetfairToFpdb.py +++ b/pyfpdb/BetfairToFpdb.py @@ -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): diff --git a/pyfpdb/CarbonToFpdb.py b/pyfpdb/CarbonToFpdb.py index 32ad2ef8..9239ab12 100644 --- a/pyfpdb/CarbonToFpdb.py +++ b/pyfpdb/CarbonToFpdb.py @@ -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 diff --git a/pyfpdb/Card.py b/pyfpdb/Card.py index 2262a720..eae7447f 100755 --- a/pyfpdb/Card.py +++ b/pyfpdb/Card.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- #Copyright 2008-2010 Carl Gherardi diff --git a/pyfpdb/Charset.py b/pyfpdb/Charset.py index 4341aa4f..fcba6f98 100644 --- a/pyfpdb/Charset.py +++ b/pyfpdb/Charset.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- #Copyright 2010 Mika Bostrom diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 6a71a750..b5e3e76f 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -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): diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index b68d9f49..c7e00190 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -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) 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 diff --git a/pyfpdb/DatabaseManager.py b/pyfpdb/DatabaseManager.py deleted file mode 100644 index fb52af63..00000000 --- a/pyfpdb/DatabaseManager.py +++ /dev/null @@ -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 . -#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 - # 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() diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 7154696a..7aab6c98 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -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'): diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index ee6b3601..b16980ed 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -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): diff --git a/pyfpdb/Exceptions.py b/pyfpdb/Exceptions.py index af454586..34b3bade 100644 --- a/pyfpdb/Exceptions.py +++ b/pyfpdb/Exceptions.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/python # -*- coding: utf-8 -*- #Copyright 2009-2010 Matt Turnbull diff --git a/pyfpdb/Filters.py b/pyfpdb/Filters.py index 0d2f560b..06f43610 100644 --- a/pyfpdb/Filters.py +++ b/pyfpdb/Filters.py @@ -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 diff --git a/pyfpdb/FpdbSQLQueries.py b/pyfpdb/FpdbSQLQueries.py deleted file mode 100644 index fb67f560..00000000 --- a/pyfpdb/FpdbSQLQueries.py +++ /dev/null @@ -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 . -#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] diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index 4ab962f0..290f2f42 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -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[.0-9]+)/\$?(?P[.0-9]+)\s(Ante\s\$?(?P[.0-9]+)\s)?-\s \$?(?P[.0-9]+\sCap\s)? (?P[a-zA-Z\/\'\s]+)\s-\s - (?P\d+:\d+:\d+\s\w+\s-\s\d+/\d+/\d+)\s? + (?P\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\))?\n (?:.*?\n(?PHand\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