merge from Eric

This commit is contained in:
sqlcoder 2009-09-26 11:45:05 +01:00
commit 518fd9e039
19 changed files with 485 additions and 256 deletions

View File

@ -0,0 +1,5 @@
free-poker-tools (0.10.99) unstable; urgency=low
* Initial packaging release.
-- Mika Bostrom <bostik+fpdb@bostik.iki.fi> Thu, 20 Aug 2009 06:30:53 +0300

1
packaging/debian/compat Normal file
View File

@ -0,0 +1 @@
7

24
packaging/debian/control Normal file
View File

@ -0,0 +1,24 @@
Source: free-poker-tools
Maintainer: Mika Bostrom <bostik+fpdb@bostik.iki.fi>
Section: games
Priority: extra
Build-Depends: debhelper, python-support
Standards-Version: 3.8.0
Package: python-fpdb
Architecture: any
Section: games
Priority: extra
Depends: ${python:Depends}, python-gtk2, python-matplotlib,
python-support, mysql-server | postgresql | python-pysqlite2,
python-psycopg2 | python-mysqldb
Suggests: wine
Description: free poker database with HUD
FPDB is a statistics tool for online poker. It supports most sites
and several games. Most prominent feature is its heads-up display
(HUD) which shows statistical details for players in real time.
.
Due to the fact that most online poker clients are Windows-only,
you may need to install wine.
.
FPDB is under heavy development.

View File

@ -0,0 +1,7 @@
This package was debianised by Mika Bostrom <bostik+fpdb@bostik.iki.fi>
Upstream authors: ...
License: AGPL
Copyright (C) 2008- The FPDB developers

1
packaging/debian/links Normal file
View File

@ -0,0 +1 @@
/usr/share/python-support/python-fpdb/fpdb/fpdb.py /usr/bin/fpdb

View File

@ -0,0 +1,5 @@
#!/bin/sh
# When installed into .../fpdb/ the script gets mode 644
# Note: "dh_fixperms -Xfpdb.py" did not work, hence this hack
chmod 755 /usr/bin/fpdb

View File

@ -0,0 +1 @@
2.4-

45
packaging/debian/rules Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/make -f
# -*- makefile -*-
PACKAGE := python-fpdb
build: build-stamp
build-stamp:
dh_testdir
python setup.py build
touch $@
clean:
dh_testdir
dh_testroot
python setup.py clean
rm -rf build
dh_clean build-stamp
install: build
dh_testdir
dh_testroot
dh_prep || dh_clean -k
dh_installdirs
#
python setup.py install --root=debian/$(PACKAGE) --prefix=/usr --no-compile
binary-indep: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_link
dh_compress
dh_fixperms
dh_pysupport
dh_installdeb
dh_gencontrol
dh_md5sums
dh_builddeb
binary-arch: build install
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install

View File

@ -24,6 +24,7 @@ Handles HUD configuration files.
######################################################################## ########################################################################
# Standard Library modules # Standard Library modules
from __future__ import with_statement
import os import os
import sys import sys
import inspect import inspect
@ -316,8 +317,8 @@ class Config:
# Parse even if there was no real config file found and we are using the example # Parse even if there was no real config file found and we are using the example
# If using the example, we'll edit it later # If using the example, we'll edit it later
log.info("Reading configuration file %s" % file)
try: try:
log.info("Reading configuration file %s" % (file))
doc = xml.dom.minidom.parse(file) doc = xml.dom.minidom.parse(file)
except: except:
log.error("Error parsing %s. See error log file." % (file)) log.error("Error parsing %s. See error log file." % (file))
@ -353,20 +354,22 @@ class Config:
for db_node in doc.getElementsByTagName("database"): for db_node in doc.getElementsByTagName("database"):
try: try:
db = Database(node = db_node) db = Database(node = db_node)
except:
raise FpdbError("Unable to create database object")
else:
if db.db_name in self.supported_databases: if db.db_name in self.supported_databases:
raise FpdbError("Database names must be unique") raise FpdbError("Database names must be unique")
# If there is only one Database node, or none are marked default, the first is selected # If there is only one Database node, or none are marked
if len(self.supported_databases) == 0: # default, use first
if not self.supported_databases:
self.db_selected = db.db_name self.db_selected = db.db_name
self.supported_databases[db.db_name] = db self.supported_databases[db.db_name] = db
if db.db_selected: if db.db_selected:
self.db_selected = db.db_name self.db_selected = db.db_name
except:
raise
if dbname and dbname in self.supported_databases: if dbname and dbname in self.supported_databases:
self.db_selected = dbname self.db_selected = dbname
# s_dbs = doc.getElementsByTagName("mucked_windows") # s_dbs = doc.getElementsByTagName("mucked_windows")
for aw_node in doc.getElementsByTagName("aw"): for aw_node in doc.getElementsByTagName("aw"):
aw = Aux_window(node = aw_node) aw = Aux_window(node = aw_node)
@ -442,12 +445,11 @@ class Config:
def read_default_conf(self, file): def read_default_conf(self, file):
parms = {} parms = {}
fh = open(file, "r") with open(file, "r") as fh:
for line in fh: for line in fh:
line = string.strip(line) line = string.strip(line)
(key, value) = line.split('=') (key, value) = line.split('=')
parms[key] = value parms[key] = value
fh.close
return parms return parms
def find_example_config(self): def find_example_config(self):
@ -496,14 +498,12 @@ class Config:
def save(self, file = None): def save(self, file = None):
if file != None: if file != None:
f = open(file, 'w') with open(file, 'w') as f:
self.doc.writexml(f) self.doc.writexml(f)
f.close()
else: else:
shutil.move(self.file, self.file+".backup") shutil.move(self.file, self.file+".backup")
f = open(self.file, 'w') with open(self.file, 'w') as f:
self.doc.writexml(f) self.doc.writexml(f)
f.close
def edit_layout(self, site_name, max, width = None, height = None, def edit_layout(self, site_name, max, width = None, height = None,
fav_seat = None, locations = None): fav_seat = None, locations = None):
@ -535,6 +535,7 @@ class Config:
def get_db_parameters(self): def get_db_parameters(self):
db = {} db = {}
name = self.db_selected name = self.db_selected
# TODO: What's up with all the exception handling here?!
try: db['db-databaseName'] = name try: db['db-databaseName'] = name
except: pass except: pass

View File

@ -181,7 +181,7 @@ class Database:
# create index indexname on tablename (col); # create index indexname on tablename (col);
def __init__(self, c, db_name = None, game = None, sql = None): # db_name and game not used any more def __init__(self, c, sql = None):
log.info("Creating Database instance, sql = %s" % sql) log.info("Creating Database instance, sql = %s" % sql)
self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql
self.fdb.do_connect(c) self.fdb.do_connect(c)
@ -201,7 +201,7 @@ class Database:
# where possible avoid creating new SQL instance by using the global one passed in # where possible avoid creating new SQL instance by using the global one passed in
if sql == None: if sql is None:
self.sql = SQL.Sql(type = self.type, db_server = db_params['db-server']) self.sql = SQL.Sql(type = self.type, db_server = db_params['db-server'])
else: else:
self.sql = sql self.sql = sql
@ -370,23 +370,20 @@ class Database:
def init_hud_stat_vars(self, hud_days): def init_hud_stat_vars(self, hud_days):
"""Initialise variables used by Hud to fetch stats.""" """Initialise variables used by Hud to fetch stats."""
self.hand_1day_ago = 1
try: try:
# self.hand_1day_ago used to fetch stats for current session (i.e. if hud_style = 'S')
self.hand_1day_ago = 1
c = self.get_cursor() c = self.get_cursor()
c.execute(self.sql.query['get_hand_1day_ago']) c.execute(self.sql.query['get_hand_1day_ago'])
row = c.fetchone() row = c.fetchone()
except: # TODO: what error is a database error?!
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
else:
if row and row[0]: if row and row[0]:
self.hand_1day_ago = row[0] self.hand_1_day_ago = row[0]
#print "hand 1day ago =", self.hand_1day_ago
# self.date_ndays_ago used if hud_style = 'T'
d = timedelta(days=hud_days) d = timedelta(days=hud_days)
now = datetime.utcnow() - d now = datetime.utcnow() - d
self.date_ndays_ago = "d%02d%02d%02d" % (now.year-2000, now.month, now.day) self.date_ndays_ago = "d%02d%02d%02d" % (now.year - 2000, now.month, now.day)
except:
err = traceback.extract_tb(sys.exc_info()[2])[-1]
print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
def init_player_hud_stat_vars(self, playerid): def init_player_hud_stat_vars(self, playerid):
# not sure if this is workable, to be continued ... # not sure if this is workable, to be continued ...
@ -1092,6 +1089,8 @@ class Database:
c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')")
if self.backend == self.SQLITE: if self.backend == self.SQLITE:
c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);") c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
elif self.backend == self.PGSQL:
c.execute("insert into TourneyTypes values (0,1,0,0,0,False,False,null,False,False,False);")
else: else:
c.execute("""insert into TourneyTypes(id, siteId, buyin, fee, maxSeats, knockout c.execute("""insert into TourneyTypes(id, siteId, buyin, fee, maxSeats, knockout
,rebuyOrAddon, speed, headsUp, shootout, matrix) ,rebuyOrAddon, speed, headsUp, shootout, matrix)
@ -1263,6 +1262,10 @@ class Database:
return result return result
#end def store_the_hand #end def store_the_hand
###########################
# NEWIMPORT CODE
###########################
def storeHand(self, p): def storeHand(self, p):
#stores into table hands: #stores into table hands:
q = """INSERT INTO Hands ( q = """INSERT INTO Hands (
@ -1338,6 +1341,109 @@ class Database:
#return getLastInsertId(backend, conn, cursor) #return getLastInsertId(backend, conn, cursor)
# def storeHand # def storeHand
def storeHandsPlayers(self, p):
#def store_hands_players_holdem_omaha(self, backend, category, hands_id, player_ids, start_cashes
# ,positions, card_values, card_suits, winnings, rakes, seatNos, hudCache):
# result=[]
#
# # postgres (and others?) needs the booleans converted to ints before saving:
# # (or we could just save them as boolean ... but then we can't sum them so easily in sql ???)
# # NO - storing booleans for now so don't need this
# #hudCacheInt = {}
# #for k,v in hudCache.iteritems():
# # if k in ('wonWhenSeenStreet1', 'wonAtSD', 'totalProfit'):
# # hudCacheInt[k] = v
# # else:
# # hudCacheInt[k] = map(lambda x: 1 if x else 0, v)
#
# try:
# inserts = []
# for i in xrange(len(player_ids)):
# card1 = Card.cardFromValueSuit(card_values[i][0], card_suits[i][0])
# card2 = Card.cardFromValueSuit(card_values[i][1], card_suits[i][1])
#
# if (category=="holdem"):
# startCards = Card.twoStartCards(card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1])
# card3 = None
# card4 = None
# elif (category=="omahahi" or category=="omahahilo"):
# startCards = Card.fourStartCards(card_values[i][0], card_suits[i][0], card_values[i][1], card_suits[i][1]
# ,card_values[i][2], card_suits[i][2], card_values[i][3], card_suits[i][3])
# card3 = Card.cardFromValueSuit(card_values[i][2], card_suits[i][2])
# card4 = Card.cardFromValueSuit(card_values[i][3], card_suits[i][3])
# else:
# raise FpdbError("invalid category")
#
# inserts.append( (
# hands_id, player_ids[i], start_cashes[i], positions[i], 1, # tourneytypeid - needed for hudcache
# card1, card2, card3, card4, startCards,
# winnings[i], rakes[i], seatNos[i], hudCache['totalProfit'][i],
# hudCache['street0VPI'][i], hudCache['street0Aggr'][i],
# hudCache['street0_3BChance'][i], hudCache['street0_3BDone'][i],
# hudCache['street1Seen'][i], hudCache['street2Seen'][i], hudCache['street3Seen'][i],
# hudCache['street4Seen'][i], hudCache['sawShowdown'][i],
# hudCache['street1Aggr'][i], hudCache['street2Aggr'][i], hudCache['street3Aggr'][i], hudCache['street4Aggr'][i],
# hudCache['otherRaisedStreet1'][i], hudCache['otherRaisedStreet2'][i],
# hudCache['otherRaisedStreet3'][i], hudCache['otherRaisedStreet4'][i],
# hudCache['foldToOtherRaisedStreet1'][i], hudCache['foldToOtherRaisedStreet2'][i],
# hudCache['foldToOtherRaisedStreet3'][i], hudCache['foldToOtherRaisedStreet4'][i],
# hudCache['wonWhenSeenStreet1'][i], hudCache['wonAtSD'][i],
# hudCache['stealAttemptChance'][i], hudCache['stealAttempted'][i], hudCache['foldBbToStealChance'][i],
# hudCache['foldedBbToSteal'][i], hudCache['foldSbToStealChance'][i], hudCache['foldedSbToSteal'][i],
# hudCache['street1CBChance'][i], hudCache['street1CBDone'][i], hudCache['street2CBChance'][i], hudCache['street2CBDone'][i],
# hudCache['street3CBChance'][i], hudCache['street3CBDone'][i], hudCache['street4CBChance'][i], hudCache['street4CBDone'][i],
# hudCache['foldToStreet1CBChance'][i], hudCache['foldToStreet1CBDone'][i],
# hudCache['foldToStreet2CBChance'][i], hudCache['foldToStreet2CBDone'][i],
# hudCache['foldToStreet3CBChance'][i], hudCache['foldToStreet3CBDone'][i],
# hudCache['foldToStreet4CBChance'][i], hudCache['foldToStreet4CBDone'][i],
# hudCache['street1CheckCallRaiseChance'][i], hudCache['street1CheckCallRaiseDone'][i],
# hudCache['street2CheckCallRaiseChance'][i], hudCache['street2CheckCallRaiseDone'][i],
# hudCache['street3CheckCallRaiseChance'][i], hudCache['street3CheckCallRaiseDone'][i],
# hudCache['street4CheckCallRaiseChance'][i], hudCache['street4CheckCallRaiseDone'][i],
# hudCache['street0Calls'][i], hudCache['street1Calls'][i], hudCache['street2Calls'][i], hudCache['street3Calls'][i], hudCache['street4Calls'][i],
# hudCache['street0Bets'][i], hudCache['street1Bets'][i], hudCache['street2Bets'][i], hudCache['street3Bets'][i], hudCache['street4Bets'][i]
# ) )
# c = self.get_cursor()
# c.executemany ("""
# INSERT INTO HandsPlayers
# (handId, playerId, startCash, position, tourneyTypeId,
# card1, card2, card3, card4, startCards, winnings, rake, seatNo, totalProfit,
# street0VPI, street0Aggr, street0_3BChance, street0_3BDone,
# street1Seen, street2Seen, street3Seen, street4Seen, sawShowdown,
# street1Aggr, street2Aggr, street3Aggr, street4Aggr,
# otherRaisedStreet1, otherRaisedStreet2, otherRaisedStreet3, otherRaisedStreet4,
# foldToOtherRaisedStreet1, foldToOtherRaisedStreet2, foldToOtherRaisedStreet3, foldToOtherRaisedStreet4,
# wonWhenSeenStreet1, wonAtSD,
# stealAttemptChance, stealAttempted, foldBbToStealChance, foldedBbToSteal, foldSbToStealChance, foldedSbToSteal,
# street1CBChance, street1CBDone, street2CBChance, street2CBDone,
# street3CBChance, street3CBDone, street4CBChance, street4CBDone,
# foldToStreet1CBChance, foldToStreet1CBDone, foldToStreet2CBChance, foldToStreet2CBDone,
# foldToStreet3CBChance, foldToStreet3CBDone, foldToStreet4CBChance, foldToStreet4CBDone,
# street1CheckCallRaiseChance, street1CheckCallRaiseDone, street2CheckCallRaiseChance, street2CheckCallRaiseDone,
# street3CheckCallRaiseChance, street3CheckCallRaiseDone, street4CheckCallRaiseChance, street4CheckCallRaiseDone,
# street0Calls, street1Calls, street2Calls, street3Calls, street4Calls,
# street0Bets, street1Bets, street2Bets, street3Bets, street4Bets
# )
# VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
# %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
# %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
# %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""".replace('%s', self.sql.query['placeholder'])
# ,inserts )
# result.append( self.get_last_insert_id(c) ) # wrong? not used currently
# except:
# raise FpdbError( "store_hands_players_holdem_omaha error: " + str(sys.exc_value) )
#
# return result
pass
#################################
# Finish of NEWIMPORT CODE
#################################
def storeHands(self, backend, site_hand_no, gametype_id def storeHands(self, backend, site_hand_no, gametype_id
,hand_start_time, names, tableName, maxSeats, hudCache ,hand_start_time, names, tableName, maxSeats, hudCache
,board_values, board_suits): ,board_values, board_suits):

4
pyfpdb/FulltiltToFpdb.py Executable file → Normal file
View File

@ -656,7 +656,9 @@ class Fulltilt(HandHistoryConverter):
heroName = n.group('HERO_NAME') heroName = n.group('HERO_NAME')
tourney.hero = heroName tourney.hero = heroName
# Is this really useful ? # Is this really useful ?
if (tourney.finishPositions[heroName] != Decimal(n.group('HERO_FINISHING_POS'))): if heroName not in tourney.finishPositions:
print heroName, "not found in tourney.finishPositions ..."
elif (tourney.finishPositions[heroName] != Decimal(n.group('HERO_FINISHING_POS'))):
print "Bad parsing : finish position incoherent : %s / %s" % (tourney.finishPositions[heroName], n.group('HERO_FINISHING_POS')) print "Bad parsing : finish position incoherent : %s / %s" % (tourney.finishPositions[heroName], n.group('HERO_FINISHING_POS'))
return True return True

View File

@ -60,6 +60,11 @@ class GuiBulkImport():
return True return True
def load_clicked(self, widget, data=None): def load_clicked(self, widget, data=None):
stored = None
dups = None
partial = None
errs = None
ttime = None
# Does the lock acquisition need to be more sophisticated for multiple dirs? # Does the lock acquisition need to be more sophisticated for multiple dirs?
# (see comment above about what to do if pipe already open) # (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(False): # returns false immediately if lock not acquired
@ -101,13 +106,13 @@ class GuiBulkImport():
self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename) self.importer.addBulkImportImportFileOrDir(self.inputFile, site = sitename)
self.importer.setCallHud(False) self.importer.setCallHud(False)
starttime = time() starttime = time()
try: # try:
(stored, dups, partial, errs, ttime) = self.importer.runImport() (stored, dups, partial, errs, ttime) = self.importer.runImport()
except: # except:
print "*** EXCEPTION DURING BULKIMPORT!!!" # print "*** EXCEPTION DURING BULKIMPORT!!!"
raise Exceptions.FpdbError # raise Exceptions.FpdbError
finally: # finally:
gobject.source_remove(self.timer) gobject.source_remove(self.timer)
ttime = time() - starttime ttime = time() - starttime
if ttime == 0: if ttime == 0:

View File

@ -32,7 +32,7 @@ try:
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
from numpy import arange, cumsum from numpy import arange, cumsum
from pylab import * from pylab import *
except: except ImportError:
print """Failed to load libs for graphing, graphing will not function. Please in print """Failed to load libs for graphing, graphing will not function. Please in
stall numpy and matplotlib if you want to use graphs.""" stall numpy and matplotlib if you want to use graphs."""
print """This is of no consequence for other parts of the program, e.g. import print """This is of no consequence for other parts of the program, e.g. import

View File

@ -339,11 +339,16 @@ class Hud:
self.update_table_position() self.update_table_position()
for s in self.stat_dict: for s in self.stat_dict:
statd = self.stat_dict[s] try:
statd = self.stat_dict[s]
except KeyError:
print "KeyError at the start of the for loop in update in hud_main. How this can possibly happen is totally beyond my comprehension. Your HUD may be about to get really weird. -Eric"
print "(btw, the key was ", s, " and statd is...", statd
continue
try: try:
self.stat_windows[statd['seat']].player_id = statd['player_id'] self.stat_windows[statd['seat']].player_id = statd['player_id']
#self.stat_windows[self.stat_dict[s]['seat']].player_id = self.stat_dict[s]['player_id'] #self.stat_windows[self.stat_dict[s]['seat']].player_id = self.stat_dict[s]['player_id']
except: # omg, we have more seats than stat windows .. damn poker sites with incorrect max seating info .. let's force 10 here except KeyError: # omg, we have more seats than stat windows .. damn poker sites with incorrect max seating info .. let's force 10 here
self.max = 10 self.max = 10
self.create(hand, config, self.stat_dict, self.cards) self.create(hand, config, self.stat_dict, self.cards)
self.stat_windows[statd['seat']].player_id = statd['player_id'] self.stat_windows[statd['seat']].player_id = statd['player_id']
@ -379,6 +384,7 @@ class Stat_Window:
# This handles all callbacks from button presses on the event boxes in # This handles all callbacks from button presses on the event boxes in
# the stat windows. There is a bit of an ugly kludge to separate single- # the stat windows. There is a bit of an ugly kludge to separate single-
# and double-clicks. # and double-clicks.
self.window.show_all()
if event.button == 3: # right button event if event.button == 3: # right button event
newpopup = Popup_window(self.window, self) newpopup = Popup_window(self.window, self)
@ -393,7 +399,6 @@ class Stat_Window:
if event.button == 1: # left button event if event.button == 1: # left button event
# TODO: make position saving save sizes as well? # TODO: make position saving save sizes as well?
self.window.show_all()
if event.state & gtk.gdk.SHIFT_MASK: if event.state & gtk.gdk.SHIFT_MASK:
self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time) self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time)
else: else:

View File

@ -21,19 +21,18 @@ import re
import sys import sys
import logging import logging
from time import time, strftime from time import time, strftime
from Exceptions import *
use_pool = False
try: try:
import sqlalchemy.pool as pool import sqlalchemy.pool as pool
use_pool = True use_pool = True
except: except ImportError:
logging.info("Not using sqlalchemy connection pool.") logging.info("Not using sqlalchemy connection pool.")
use_pool = False
import fpdb_simple import fpdb_simple
import FpdbSQLQueries import FpdbSQLQueries
from Exceptions import *
class fpdb_db: class fpdb_db:
MYSQL_INNODB = 2 MYSQL_INNODB = 2
@ -69,12 +68,12 @@ class fpdb_db:
"""Connects a database with the given parameters""" """Connects a database with the given parameters"""
if backend is None: if backend is None:
raise FpdbError('Database backend not defined') raise FpdbError('Database backend not defined')
self.backend=backend self.backend = backend
self.host=host self.host = host
self.user=user self.user = user
self.password=password self.password = password
self.database=database self.database = database
if backend==fpdb_db.MYSQL_INNODB: if backend == fpdb_db.MYSQL_INNODB:
import MySQLdb import MySQLdb
if use_pool: if use_pool:
MySQLdb = pool.manage(MySQLdb, pool_size=5) MySQLdb = pool.manage(MySQLdb, pool_size=5)
@ -115,7 +114,7 @@ class fpdb_db:
msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user) msg = "PostgreSQL connection to database (%s) user (%s) failed." % (database, user)
print msg print msg
raise FpdbError(msg) raise FpdbError(msg)
elif backend==fpdb_db.SQLITE: elif backend == fpdb_db.SQLITE:
logging.info("Connecting to SQLite:%(database)s" % {'database':database}) logging.info("Connecting to SQLite:%(database)s" % {'database':database})
import sqlite3 import sqlite3
if use_pool: if use_pool:
@ -132,20 +131,20 @@ class fpdb_db:
sqlite3.register_adapter(bool, lambda x: "1" if x else "0") sqlite3.register_adapter(bool, lambda x: "1" if x else "0")
else: else:
raise FpdbError("unrecognised database backend:"+backend) raise FpdbError("unrecognised database backend:"+backend)
self.cursor=self.db.cursor() self.cursor = self.db.cursor()
# Set up query dictionary as early in the connection process as we can. # Set up query dictionary as early in the connection process as we can.
self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name())
self.cursor.execute(self.sql.query['set tx level']) self.cursor.execute(self.sql.query['set tx level'])
self.wrongDbVersion=False self.wrongDbVersion = False
try: try:
self.cursor.execute("SELECT * FROM Settings") self.cursor.execute("SELECT * FROM Settings")
settings=self.cursor.fetchone() settings = self.cursor.fetchone()
if settings[0]!=118: if settings[0] != 118:
print "outdated or too new database version - please recreate tables" print "outdated or too new database version - please recreate tables"
self.wrongDbVersion=True self.wrongDbVersion = True
except:# _mysql_exceptions.ProgrammingError: except:# _mysql_exceptions.ProgrammingError:
if database != ":memory:": print "failed to read settings table - please recreate tables" if database != ":memory:": print "failed to read settings table - please recreate tables"
self.wrongDbVersion=True self.wrongDbVersion = True
#end def connect #end def connect
def disconnect(self, due_to_error=False): def disconnect(self, due_to_error=False):

View File

@ -49,22 +49,20 @@ log = logging.getLogger('importer')
# database interface modules # database interface modules
try: try:
import MySQLdb import MySQLdb
mysqlLibFound=True except ImportError:
log.debug("Import module: MySQLdb") log.debug("Import database module: MySQLdb not found")
except: else:
log.debug("Import module: MySQLdb not found") mysqlLibFound = True
try: try:
import psycopg2 import psycopg2
pgsqlLibFound=True except ImportError:
log.debug("Import database module: psycopg2 not found")
else:
import psycopg2.extensions import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
log.debug("Import module: psycopg2")
except:
log.debug("Import module: psycopg2 not found")
class Importer: class Importer:
def __init__(self, caller, settings, config, sql = None): def __init__(self, caller, settings, config, sql = None):
"""Constructor""" """Constructor"""
self.settings = settings self.settings = settings

View File

@ -18,46 +18,63 @@
#parses an in-memory fpdb hand history and calls db routine to store it #parses an in-memory fpdb hand history and calls db routine to store it
import sys import sys
import fpdb_simple
import Database
from time import time, strftime from time import time, strftime
from Exceptions import * from Exceptions import *
import fpdb_simple
import Database
#parses a holdem hand
def mainParser(settings, siteID, category, hand, config, db = None, writeq = None): def mainParser(settings, siteID, category, hand, config, db = None, writeq = None):
""" mainParser for Holdem Hands """
t0 = time() t0 = time()
#print "mainparser"
backend = settings['db-backend'] backend = settings['db-backend']
# Ideally db connection is passed in, if not use sql list if passed in, otherwise start from scratch # Ideally db connection is passed in, if not use sql list if passed in,
# otherwise start from scratch
if db == None: if db == None:
db = Database.Database(c = config, sql = None) db = Database.Database(c = config, sql = None)
category = fpdb_simple.recogniseCategory(hand[0]) category = fpdb_simple.recogniseCategory(hand[0])
base = "hold" if category == "holdem" or category == "omahahi" or category == "omahahilo" else "stud" base = "hold" if (category == "holdem" or category == "omahahi" or
category == "omahahilo") else "stud"
#part 0: create the empty arrays #part 0: create the empty arrays
lineTypes = [] #char, valid values: header, name, cards, action, win, rake, ignore # lineTypes valid values: header, name, cards, action, win, rake, ignore
lineStreets = [] #char, valid values: (predeal, preflop, flop, turn, river) # lineStreets valid values: predeal, preflop, flop, turn, river
lineTypes = []
cardValues, cardSuits, boardValues, boardSuits, antes, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo, seatLines, winnings, rakes=[],[],[],[],[],[],[],[],[],[],[],[],[] lineStreets = []
cardValues = []
cardSuits = []
boardValues = []
boardSuits = []
antes = []
allIns = []
actionAmounts = []
actionNos = []
actionTypes = []
actionTypeByNo = []
seatLines = []
winnings = []
rakes = []
#part 1: read hand no and check for duplicate #part 1: read hand no and check for duplicate
siteHandNo = fpdb_simple.parseSiteHandNo(hand[0]) siteHandNo = fpdb_simple.parseSiteHandNo(hand[0])
#print "siteHandNo =", siteHandNo handStartTime = fpdb_simple.parseHandStartTime(hand[0])
handStartTime = fpdb_simple.parseHandStartTime(hand[0])
isTourney = fpdb_simple.isTourney(hand[0]) isTourney = fpdb_simple.isTourney(hand[0])
smallBlindLine = 0
smallBlindLine = None
for i, line in enumerate(hand): for i, line in enumerate(hand):
if 'posts small blind' in line or 'posts the small blind' in line: if 'posts small blind' in line or 'posts the small blind' in line:
if line[-2:] == "$0": continue if line[-2:] == "$0": continue
smallBlindLine = i smallBlindLine = i
break break
else:
smallBlindLine = 0
# If we did not find a small blind line, what happens?
# if we leave it at None, it errors two lines down.
gametypeID = fpdb_simple.recogniseGametypeID(backend, db, db.get_cursor(), hand[0], hand[smallBlindLine], siteID, category, isTourney) gametypeID = fpdb_simple.recogniseGametypeID(backend, db, db.get_cursor(),
hand[0], hand[smallBlindLine],
siteID, category, isTourney)
if isTourney: if isTourney:
siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0]) siteTourneyNo = fpdb_simple.parseTourneyNo(hand[0])
buyin = fpdb_simple.parseBuyin(hand[0]) buyin = fpdb_simple.parseBuyin(hand[0])
@ -68,8 +85,14 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
tourneyStartTime= handStartTime #todo: read tourney start time tourneyStartTime= handStartTime #todo: read tourney start time
rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0]) rebuyOrAddon = fpdb_simple.isRebuyOrAddon(hand[0])
## The tourney site id has to be searched because it may already be in db with a TourneyTypeId which is different from the one automatically calculated (Summary import first) # The tourney site id has to be searched because it may already be in
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db, siteID, siteTourneyNo, buyin, fee, knockout, rebuyOrAddon) # db with a TourneyTypeId which is different from the one automatically
# calculated (Summary import first)
tourneyTypeId = fpdb_simple.recogniseTourneyTypeId(db, siteID,
siteTourneyNo,
buyin, fee,
knockout,
rebuyOrAddon)
else: else:
siteTourneyNo = -1 siteTourneyNo = -1
buyin = -1 buyin = -1
@ -100,7 +123,9 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
startCashes = tmp['startCashes'] startCashes = tmp['startCashes']
seatNos = tmp['seatNos'] seatNos = tmp['seatNos']
fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes, winnings, rakes, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo) fpdb_simple.createArrays(category, len(names), cardValues, cardSuits, antes,
winnings, rakes, actionTypes, allIns,
actionAmounts, actionNos, actionTypeByNo)
#3b read positions #3b read positions
if base == "hold": if base == "hold":
@ -109,27 +134,32 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
#part 4: take appropriate action for each line based on linetype #part 4: take appropriate action for each line based on linetype
for i, line in enumerate(hand): for i, line in enumerate(hand):
if lineTypes[i] == "cards": if lineTypes[i] == "cards":
fpdb_simple.parseCardLine(category, lineStreets[i], line, names, cardValues, cardSuits, boardValues, boardSuits) fpdb_simple.parseCardLine(category, lineStreets[i], line, names,
cardValues, cardSuits, boardValues,
boardSuits)
#if category=="studhilo": #if category=="studhilo":
# print "hand[i]:", hand[i] # print "hand[i]:", hand[i]
# print "cardValues:", cardValues # print "cardValues:", cardValues
# print "cardSuits:", cardSuits # print "cardSuits:", cardSuits
elif lineTypes[i] == "action": elif lineTypes[i] == "action":
fpdb_simple.parseActionLine(base, isTourney, line, lineStreets[i], playerIDs, names, actionTypes, allIns, actionAmounts, actionNos, actionTypeByNo) fpdb_simple.parseActionLine(base, isTourney, line, lineStreets[i],
playerIDs, names, actionTypes, allIns,
actionAmounts, actionNos, actionTypeByNo)
elif lineTypes[i] == "win": elif lineTypes[i] == "win":
fpdb_simple.parseWinLine(line, names, winnings, isTourney) fpdb_simple.parseWinLine(line, names, winnings, isTourney)
elif lineTypes[i] == "rake": elif lineTypes[i] == "rake":
totalRake = 0 if isTourney else fpdb_simple.parseRake(line) totalRake = 0 if isTourney else fpdb_simple.parseRake(line)
fpdb_simple.splitRake(winnings, rakes, totalRake) fpdb_simple.splitRake(winnings, rakes, totalRake)
elif lineTypes[i]=="header" or lineTypes[i]=="rake" or lineTypes[i]=="name" or lineTypes[i]=="ignore": elif (lineTypes[i] == "header" or lineTypes[i] == "rake" or
lineTypes[i] == "name" or lineTypes[i] == "ignore"):
pass pass
elif lineTypes[i]=="ante": elif lineTypes[i] == "ante":
fpdb_simple.parseAnteLine(line, isTourney, names, antes) fpdb_simple.parseAnteLine(line, isTourney, names, antes)
elif lineTypes[i]=="table": elif lineTypes[i] == "table":
tableResult=fpdb_simple.parseTableLine(base, line) tableResult=fpdb_simple.parseTableLine(base, line)
else: else:
raise FpdbError("unrecognised lineType:"+lineTypes[i]) raise FpdbError("unrecognised lineType:" + lineTypes[i])
maxSeats = tableResult['maxSeats'] maxSeats = tableResult['maxSeats']
tableName = tableResult['tableName'] tableName = tableResult['tableName']
#print "before part5, antes:", antes #print "before part5, antes:", antes
@ -152,26 +182,34 @@ def mainParser(settings, siteID, category, hand, config, db = None, writeq = Non
# if hold'em, use positions and not antes, if stud do not use positions, use antes # if hold'em, use positions and not antes, if stud do not use positions, use antes
# this is used for handsplayers inserts, so still needed even if hudcache update is being skipped # this is used for handsplayers inserts, so still needed even if hudcache update is being skipped
if base == "hold": if base == "hold":
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base,
, allIns, actionTypeByNo, winnings, totalWinnings, positions category, actionTypes,
, actionTypes, actionAmounts, None) allIns, actionTypeByNo,
winnings,
totalWinnings,
positions, actionTypes,
actionAmounts, None)
else: else:
hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base, category, actionTypes hudImportData = fpdb_simple.generateHudCacheData(playerIDs, base,
, allIns, actionTypeByNo, winnings, totalWinnings, None category, actionTypes,
, actionTypes, actionAmounts, antes) allIns, actionTypeByNo,
winnings,
totalWinnings, None,
actionTypes,
actionAmounts, antes)
#print "parse: hand data prepared" # only reads up to here apart from inserting new players
try: try:
db.commit() # need to commit new players as different db connection used db.commit() # need to commit new players as different db connection used
# for other writes. maybe this will change maybe not ... # for other writes. maybe this will change maybe not ...
except: except: # TODO: this really needs to be narrowed down
print "parse: error during commit: " + str(sys.exc_value) print "parse: error during commit: " + str(sys.exc_value)
# HERE's an ugly kludge to keep from failing when positions is undef # HERE's an ugly kludge to keep from failing when positions is undef
# We'll fix this by getting rid of the legacy importer. REB # We'll fix this by getting rid of the legacy importer. REB
try: try:
if positions: pass if positions:
except: pass
except NameError:
positions = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] positions = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# save data structures in a HandToWrite instance and then insert into database: # save data structures in a HandToWrite instance and then insert into database:
htw = Database.HandToWrite() htw = Database.HandToWrite()

View File

@ -53,8 +53,9 @@ def checkPositions(positions):
### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB ### RHH modified to allow for "position 9" here (pos==9 is when you're a dead hand before the BB
### eric - position 8 could be valid - if only one blind is posted, but there's still 10 people, ie a sitout is present, and the small is dead... ### eric - position 8 could be valid - if only one blind is posted, but there's still 10 people, ie a sitout is present, and the small is dead...
#classifies each line for further processing in later code. Manipulates the passed arrays.
def classifyLines(hand, category, lineTypes, lineStreets): def classifyLines(hand, category, lineTypes, lineStreets):
""" makes a list of classifications for each line for further processing
manipulates passed arrays """
currentStreet = "predeal" currentStreet = "predeal"
done = False #set this to true once we reach the last relevant line (the summary, except rake, is all repeats) done = False #set this to true once we reach the last relevant line (the summary, except rake, is all repeats)
for i, line in enumerate(hand): for i, line in enumerate(hand):
@ -114,61 +115,59 @@ def classifyLines(hand, category, lineTypes, lineStreets):
else: else:
raise FpdbError("unrecognised linetype in:"+hand[i]) raise FpdbError("unrecognised linetype in:"+hand[i])
lineStreets.append(currentStreet) lineStreets.append(currentStreet)
#end def classifyLines
def convert3B4B(category, limit_type, actionTypes, actionAmounts): def convert3B4B(category, limit_type, actionTypes, actionAmounts):
"""calculates the actual bet amounts in the given amount array and changes it accordingly.""" """calculates the actual bet amounts in the given amount array and changes it accordingly."""
for i in xrange(len(actionTypes)): for i in xrange(len(actionTypes)):
for j in xrange(len(actionTypes[i])): for j in xrange(len(actionTypes[i])):
bets=[] bets = []
for k in xrange(len(actionTypes[i][j])): for k in xrange(len(actionTypes[i][j])):
if (actionTypes[i][j][k]=="bet"): if (actionTypes[i][j][k] == "bet"):
bets.append((i,j,k)) bets.append((i,j,k))
if (len(bets)>=2): if (len(bets)>=2):
#print "len(bets) 2 or higher, need to correct it. bets:",bets,"len:",len(bets) #print "len(bets) 2 or higher, need to correct it. bets:",bets,"len:",len(bets)
for betNo in reversed(xrange (1,len(bets))): for betNo in reversed(xrange (1,len(bets))):
amount2=actionAmounts[bets[betNo][0]][bets[betNo][1]][bets[betNo][2]] amount2 = actionAmounts[bets[betNo][0]][bets[betNo][1]][bets[betNo][2]]
amount1=actionAmounts[bets[betNo-1][0]][bets[betNo-1][1]][bets[betNo-1][2]] amount1 = actionAmounts[bets[betNo-1][0]][bets[betNo-1][1]][bets[betNo-1][2]]
actionAmounts[bets[betNo][0]][bets[betNo][1]][bets[betNo][2]]=amount2-amount1 actionAmounts[bets[betNo][0]][bets[betNo][1]][bets[betNo][2]] = amount2 - amount1
#print "actionAmounts postConvert",actionAmounts
#end def convert3B4B(actionTypes, actionAmounts)
#Corrects the bet amount if the player had to pay blinds
def convertBlindBet(actionTypes, actionAmounts): def convertBlindBet(actionTypes, actionAmounts):
i=0#setting street to pre-flop """ Corrects the bet amount if the player had to pay blinds """
i = 0#setting street to pre-flop
for j in xrange(len(actionTypes[i])):#playerloop for j in xrange(len(actionTypes[i])):#playerloop
blinds=[] blinds = []
bets=[] bets = []
for k in xrange(len(actionTypes[i][j])): for k in xrange(len(actionTypes[i][j])):
if actionTypes[i][j][k] == "blind": if actionTypes[i][j][k] == "blind":
blinds.append((i,j,k)) blinds.append((i,j,k))
if blinds and actionTypes[i][j][k] == "bet": if blinds and actionTypes[i][j][k] == "bet":
# if (len(blinds)>0 and actionTypes[i][j][k]=="bet"):
bets.append((i,j,k)) bets.append((i,j,k))
if len(bets) == 1: if len(bets) == 1:
blind_amount=actionAmounts[blinds[0][0]][blinds[0][1]][blinds[0][2]] blind_amount=actionAmounts[blinds[0][0]][blinds[0][1]][blinds[0][2]]
bet_amount=actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]] bet_amount=actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]]
actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]]=bet_amount-blind_amount actionAmounts[bets[0][0]][bets[0][1]][bets[0][2]] = bet_amount - blind_amount
#end def convertBlindBet
#converts the strings in the given array to ints (changes the passed array, no returning). see table design for conversion details #converts the strings in the given array to ints (changes the passed array, no returning). see table design for conversion details
#todo: make this use convertCardValuesBoard #todo: make this use convertCardValuesBoard
def convertCardValues(arr): def convertCardValues(arr):
map(convertCardValuesBoard, arr) map(convertCardValuesBoard, arr)
#end def convertCardValues
# a 0-card is one in a stud game that we did not see or was not shown # a 0-card is one in a stud game that we did not see or was not shown
card_map = { 0: 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} card_map = { 0: 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}
#converts the strings in the given array to ints (changes the passed array, no returning). see table design for conversion details
def convertCardValuesBoard(arr): def convertCardValuesBoard(arr):
""" converts the strings in the given array to ints
(changes the passed array, no returning). see table design for
conversion details """
for i in xrange(len(arr)): for i in xrange(len(arr)):
arr[i] = card_map[arr[i]] arr[i] = card_map[arr[i]]
#end def convertCardValuesBoard
#this creates the 2D/3D arrays. manipulates the passed arrays instead of returning. def createArrays(category, seats, card_values, card_suits, antes, winnings,
def createArrays(category, seats, card_values, card_suits, antes, winnings, rakes, action_types, allIns, action_amounts, actionNos, actionTypeByNo): rakes, action_types, allIns, action_amounts, actionNos,
actionTypeByNo):
""" this creates the 2D/3D arrays. manipulates the passed arrays instead of returning. """
for i in xrange(seats):#create second dimension arrays for i in xrange(seats):#create second dimension arrays
card_values.append( [] ) card_values.append( [] )
card_suits.append( [] ) card_suits.append( [] )
@ -176,7 +175,8 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake
winnings.append(0) winnings.append(0)
rakes.append(0) rakes.append(0)
streetCount = 4 if category == "holdem" or category == "omahahi" or category == "omahahilo" else 5 streetCount = 4 if (category == "holdem" or category == "omahahi" or
category == "omahahilo") else 5
for i in xrange(streetCount): #build the first dimension array, for streets for i in xrange(streetCount): #build the first dimension array, for streets
action_types.append([]) action_types.append([])
@ -184,14 +184,14 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake
action_amounts.append([]) action_amounts.append([])
actionNos.append([]) actionNos.append([])
actionTypeByNo.append([]) actionTypeByNo.append([])
for j in xrange (seats): #second dimension arrays: players for j in xrange(seats): # second dimension arrays: players
action_types[i].append([]) action_types[i].append([])
allIns[i].append([]) allIns[i].append([])
action_amounts[i].append([]) action_amounts[i].append([])
actionNos[i].append([]) actionNos[i].append([])
# if (category=="holdem" or category=="omahahi" or category=="omahahilo"): # if (category=="holdem" or category=="omahahi" or category=="omahahilo"):
# pass # pass
if category=="razz" or category=="studhi" or category=="studhilo":#need to fill card arrays. if category == "razz" or category == "studhi" or category == "studhilo": #need to fill card arrays.
for i in xrange(seats): for i in xrange(seats):
for j in xrange(7): for j in xrange(7):
card_values[i].append(0) card_values[i].append(0)
@ -201,25 +201,24 @@ def createArrays(category, seats, card_values, card_suits, antes, winnings, rake
#end def createArrays #end def createArrays
def fill_board_cards(board_values, board_suits): def fill_board_cards(board_values, board_suits):
#fill up the two board card arrays """ fill up the two board card arrays """
while (len(board_values)<5): while len(board_values) < 5:
board_values.append(0) board_values.append(0)
board_suits.append("x") board_suits.append("x")
#end def fill_board_cards
def fillCardArrays(player_count, base, category, card_values, card_suits): def fillCardArrays(player_count, base, category, card_values, card_suits):
"""fills up the two card arrays""" """fills up the two card arrays"""
if (category=="holdem"): if category == "holdem":
cardCount = 2 cardCount = 2
elif (category=="omahahi" or category=="omahahilo"): elif category == "omahahi" or category == "omahahilo":
cardCount = 4 cardCount = 4
elif base=="stud": elif base == "stud":
cardCount = 7 cardCount = 7
else: else:
raise FpdbError("invalid category:", category) raise FpdbError("invalid category:", category)
for i in xrange(player_count): for i in xrange(player_count):
while (len(card_values[i]) < cardCount): while len(card_values[i]) < cardCount:
card_values[i].append(0) card_values[i].append(0)
card_suits[i].append("x") card_suits[i].append("x")
#end def fillCardArrays #end def fillCardArrays
@ -230,12 +229,12 @@ def filterAnteBlindFold(hand):
#todo: this'll only get rid of one ante folder, not multiple ones #todo: this'll only get rid of one ante folder, not multiple ones
#todo: in tourneys this should not be removed but #todo: in tourneys this should not be removed but
#print "start of filterAnteBlindFold" #print "start of filterAnteBlindFold"
pre3rd=[] pre3rd = []
for i, line in enumerate(hand): for i, line in enumerate(hand):
if line.startswith("*** 3") or line.startswith("*** HOLE"): if line.startswith("*** 3") or line.startswith("*** HOLE"):
pre3rd = hand[0:i] pre3rd = hand[0:i]
foldeeName=None foldeeName = None
for line in pre3rd: for line in pre3rd:
if line.endswith("folds") or line.endswith("is sitting out") or line.endswith(" stands up"): #found ante fold or timeout if line.endswith("folds") or line.endswith("is sitting out") or line.endswith(" stands up"): #found ante fold or timeout
pos = line.find(" folds") pos = line.find(" folds")
@ -251,26 +250,25 @@ def filterAnteBlindFold(hand):
pos2 = line.find(" (") pos2 = line.find(" (")
foldeeName = line[pos1:pos2] foldeeName = line[pos1:pos2]
if foldeeName!=None: if foldeeName is not None:
#print "filterAnteBlindFold, foldeeName:",foldeeName #print "filterAnteBlindFold, foldeeName:",foldeeName
for i, line in enumerate(hand): for i, line in enumerate(hand):
if foldeeName in line: if foldeeName in line:
hand[i] = None hand[i] = None
return [line for line in hand if line] return [line for line in hand if line]
#end def filterAnteFold
def stripEOLspaces(str): def stripEOLspaces(str):
return str.rstrip() return str.rstrip()
#removes useless lines as well as trailing spaces
def filterCrap(hand, isTourney): def filterCrap(hand, isTourney):
#remove two trailing spaces at end of line """ removes useless lines as well as trailing spaces """
#remove trailing spaces at end of line
hand = [line.rstrip() for line in hand] hand = [line.rstrip() for line in hand]
#print "hand after trailing space removal in filterCrap:",hand
#general variable position word filter/string filter #general variable position word filter/string filter
for i in xrange (len(hand)): for i in xrange(len(hand)):
if hand[i].startswith("Board ["): if hand[i].startswith("Board ["):
hand[i] = False hand[i] = False
elif hand[i].find(" out of hand ")!=-1: elif hand[i].find(" out of hand ")!=-1:
@ -347,15 +345,16 @@ def filterCrap(hand, isTourney):
hand[i] = False hand[i] = False
elif (hand[i].endswith(" is sitting out")): elif (hand[i].endswith(" is sitting out")):
hand[i] = False hand[i] = False
# python docs say this is identical to filter(None, list)
hand = [line for line in hand if line] # python docs say this is identical to filter(None, list) # which removes all false items from the passed list (hand)
hand = [line for line in hand if line]
#print "done with filterCrap, hand:", hand
return hand return hand
#end filterCrap
#takes a poker float (including , for thousand seperator and converts it to an int
def float2int(string): def float2int(string):
""" takes a poker float (including , for thousand seperator) and
converts it to an int """
# Note that this automagically assumes US style currency formatters
pos = string.find(",") pos = string.find(",")
if pos != -1: #remove , the thousand seperator if pos != -1: #remove , the thousand seperator
string = "%s%s" % (string[0:pos], string[pos+1:]) string = "%s%s" % (string[0:pos], string[pos+1:])
@ -368,56 +367,46 @@ def float2int(string):
if pos == -1: #no decimal point - was in full dollars - need to multiply with 100 if pos == -1: #no decimal point - was in full dollars - need to multiply with 100
result *= 100 result *= 100
return result return result
#end def float2int
ActionLines = ( "calls $", ": calls ", "brings in for", "completes it to", "posts small blind", ActionLines = ( "calls $", ": calls ", "brings in for", "completes it to",
"posts the small blind", "posts big blind", "posts the big blind", "posts small blind", "posts the small blind", "posts big blind",
"posts small & big blinds", "posts $", "posts a dead", "bets $", "posts the big blind", "posts small & big blinds", "posts $",
": bets ", " raises") "posts a dead", "bets $", ": bets ", " raises")
#returns boolean whether the passed line is an action line
def isActionLine(line): def isActionLine(line):
if (line.endswith("folds")): if line.endswith("folds"):
return True return True
elif (line.endswith("checks")): elif line.endswith("checks"):
return True return True
elif (line.startswith("Uncalled bet")): elif line.startswith("Uncalled bet"):
return True return True
# searches for each member of ActionLines being in line, returns true
# on first match .. neat func
return any(x for x in ActionLines if x in line) return any(x for x in ActionLines if x in line)
# return bool([ x for x in ActionLines if x in line])
# ret = any(True for searchstr in ActionLines if searchstr in line)
# ret = len( [ x for x in ActionLines if line.find(x) > -1] ) > 0
# ret = any(searchstr in line for searchstr in ActionLines)
#end def isActionLine
#returns whether this is a duplicate
def isAlreadyInDB(db, gametypeID, siteHandNo): def isAlreadyInDB(db, gametypeID, siteHandNo):
#print "isAlreadyInDB gtid,shand:",gametypeID, siteHandNo
c = db.get_cursor() c = db.get_cursor()
c.execute( db.sql.query['isAlreadyInDB'], (gametypeID, siteHandNo)) c.execute(db.sql.query['isAlreadyInDB'], (gametypeID, siteHandNo))
result = c.fetchall() result = c.fetchall()
if (len(result)>=1): if len(result) >= 1:
raise DuplicateError ("dupl") raise DuplicateError ("dupl")
#end isAlreadyInDB
def isRebuyOrAddon(topline): def isRebuyOrAddon(topline):
"""isRebuyOrAddon not implemented yet""" """isRebuyOrAddon not implemented yet"""
return False return False
#end def isRebuyOrAddon
#returns whether the passed topline indicates a tournament or not #returns whether the passed topline indicates a tournament or not
def isTourney(topline): def isTourney(topline):
return "Tournament" in topline return "Tournament" in topline
#end def isTourney
WinLines = ( "wins the pot", "ties for the ", "wins side pot", "wins the low main pot", "wins the high main pot", WinLines = ( "wins the pot", "ties for the ", "wins side pot", "wins the low main pot", "wins the high main pot",
"wins the low", "wins the low",
"wins the high pot", "wins the high side pot", "wins the main pot", "wins the side pot", "collected" ) "wins the high pot", "wins the high side pot", "wins the main pot", "wins the side pot", "collected" )
#returns boolean whether the passed line is a win line
def isWinLine(line): def isWinLine(line):
""" returns boolean whether the passed line is a win line """
return any(x for x in WinLines if x in line) return any(x for x in WinLines if x in line)
#end def isWinLine
#returns the amount of cash/chips put into the put in the given action line #returns the amount of cash/chips put into the put in the given action line
def parseActionAmount(line, atype, isTourney): def parseActionAmount(line, atype, isTourney):
@ -426,7 +415,8 @@ def parseActionAmount(line, atype, isTourney):
#elif (line.endswith(", and is all in")): #elif (line.endswith(", and is all in")):
# line=line[:-15] # line=line[:-15]
if line.endswith(", and is capped"):#ideally we should recognise this as an all-in if category is capXl #ideally we should recognise this as an all-in if category is capXl
if line.endswith(", and is capped"):
line=line[:-15] line=line[:-15]
if line.endswith(" and is capped"): if line.endswith(" and is capped"):
line=line[:-14] line=line[:-14]
@ -439,9 +429,9 @@ def parseActionAmount(line, atype, isTourney):
pos1 = line.find("(") + 1 pos1 = line.find("(") + 1
pos2 = line.find(")") pos2 = line.find(")")
amount = float2int(line[pos1:pos2]) amount = float2int(line[pos1:pos2])
elif atype == "bet" and line.find(": raises $")!=-1 and line.find("to $")!=-1: elif atype == "bet" and ": raises $" in line and "to $" in line:
pos=line.find("to $")+4 pos = line.find("to $")+4
amount=float2int(line[pos:]) amount = float2int(line[pos:])
else: else:
if not isTourney: if not isTourney:
pos = line.rfind("$")+1 pos = line.rfind("$")+1
@ -489,7 +479,6 @@ def parseActionLine(base, isTourney, line, street, playerIDs, names, action_type
actionNos[street][playerno].append(nextActionNo) actionNos[street][playerno].append(nextActionNo)
tmp=(playerIDs[playerno], atype) tmp=(playerIDs[playerno], atype)
actionTypeByNo[street].append(tmp) actionTypeByNo[street].append(tmp)
#end def parseActionLine
def goesAllInOnThisLine(line): def goesAllInOnThisLine(line):
"""returns whether the player went all-in on this line and removes the all-in text from the line.""" """returns whether the player went all-in on this line and removes the all-in text from the line."""
@ -501,7 +490,6 @@ def goesAllInOnThisLine(line):
line = line[:-15] line = line[:-15]
isAllIn = True isAllIn = True
return (line, isAllIn) return (line, isAllIn)
#end def goesAllInOnThisLine
#returns the action type code (see table design) of the given action line #returns the action type code (see table design) of the given action line
ActionTypes = { 'brings in for' :"blind", ActionTypes = { 'brings in for' :"blind",
@ -530,7 +518,6 @@ def parseActionType(line):
if x in line: if x in line:
return ActionTypes[x] return ActionTypes[x]
raise FpdbError ("failed to recognise actiontype in parseActionLine in: "+line) raise FpdbError ("failed to recognise actiontype in parseActionLine in: "+line)
#end def parseActionType
#parses the ante out of the given line and checks which player paid it, updates antes accordingly. #parses the ante out of the given line and checks which player paid it, updates antes accordingly.
def parseAnteLine(line, isTourney, names, antes): def parseAnteLine(line, isTourney, names, antes):
@ -547,15 +534,12 @@ def parseAnteLine(line, isTourney, names, antes):
pos1 = line.rfind("ante") + 5 pos1 = line.rfind("ante") + 5
pos2 = line.find(" ", pos1) pos2 = line.find(" ", pos1)
antes[i] += int(line[pos1:pos2]) antes[i] += int(line[pos1:pos2])
#print "parseAnteLine line: ", line, "antes[i]", antes[i], "antes", antes
#end def parseAntes
#returns the buyin of a tourney in cents #returns the buyin of a tourney in cents
def parseBuyin(topline): def parseBuyin(topline):
pos1 = topline.find("$")+1 pos1 = topline.find("$")+1
pos2 = topline.find("+") pos2 = topline.find("+")
return float2int(topline[pos1:pos2]) return float2int(topline[pos1:pos2])
#end def parseBuyin
#parses a card line and changes the passed arrays accordingly #parses a card line and changes the passed arrays accordingly
#todo: reorganise this messy method #todo: reorganise this messy method
@ -568,8 +552,9 @@ def parseCardLine(category, street, line, names, cardValues, cardSuits, boardVal
for i in (pos, pos+3): for i in (pos, pos+3):
cardValues[playerNo].append(line[i:i+1]) cardValues[playerNo].append(line[i:i+1])
cardSuits[playerNo].append(line[i+1:i+2]) cardSuits[playerNo].append(line[i+1:i+2])
if len(cardValues[playerNo]) !=2: if len(cardValues[playerNo]) != 2:
if cardValues[playerNo][0]==cardValues[playerNo][2] and cardSuits[playerNo][1]==cardSuits[playerNo][3]: #two tests will do if (cardValues[playerNo][0] == cardValues[playerNo][2] and
cardSuits[playerNo][1] == cardSuits[playerNo][3]):
cardValues[playerNo]=cardValues[playerNo][0:2] cardValues[playerNo]=cardValues[playerNo][0:2]
cardSuits[playerNo]=cardSuits[playerNo][0:2] cardSuits[playerNo]=cardSuits[playerNo][0:2]
else: else:
@ -580,13 +565,14 @@ def parseCardLine(category, street, line, names, cardValues, cardSuits, boardVal
cardValues[playerNo].append(line[i:i+1]) cardValues[playerNo].append(line[i:i+1])
cardSuits[playerNo].append(line[i+1:i+2]) cardSuits[playerNo].append(line[i+1:i+2])
if (len(cardValues[playerNo])!=4): if (len(cardValues[playerNo])!=4):
if cardValues[playerNo][0]==cardValues[playerNo][4] and cardSuits[playerNo][3]==cardSuits[playerNo][7]: #two tests will do if (cardValues[playerNo][0] == cardValues[playerNo][4] and
cardValues[playerNo]=cardValues[playerNo][0:4] cardSuits[playerNo][3] == cardSuits[playerNo][7]): #two tests will do
cardSuits[playerNo]=cardSuits[playerNo][0:4] cardValues[playerNo] = cardValues[playerNo][0:4]
cardSuits[playerNo] = cardSuits[playerNo][0:4]
else: else:
print "line:",line,"cardValues[playerNo]:",cardValues[playerNo] print "line:",line,"cardValues[playerNo]:",cardValues[playerNo]
raise FpdbError("read too many/too few holecards in parseCardLine") raise FpdbError("read too many/too few holecards in parseCardLine")
elif category=="razz" or category=="studhi" or category=="studhilo": elif category == "razz" or category == "studhi" or category == "studhilo":
if "shows" not in line and "mucked" not in line: if "shows" not in line and "mucked" not in line:
#print "parseCardLine(in stud if), street:", street #print "parseCardLine(in stud if), street:", street
if line[pos+2]=="]": #-> not (hero and 3rd street) if line[pos+2]=="]": #-> not (hero and 3rd street)
@ -631,7 +617,6 @@ def parseCardLine(category, street, line, names, cardValues, cardSuits, boardVal
#print boardValues #print boardValues
else: else:
raise FpdbError ("unrecognised line:"+line) raise FpdbError ("unrecognised line:"+line)
#end def parseCardLine
def parseCashesAndSeatNos(lines): def parseCashesAndSeatNos(lines):
"""parses the startCashes and seatNos of each player out of the given lines and returns them as a dictionary of two arrays""" """parses the startCashes and seatNos of each player out of the given lines and returns them as a dictionary of two arrays"""
@ -647,7 +632,6 @@ def parseCashesAndSeatNos(lines):
pos2=lines[i].find(" in chips") pos2=lines[i].find(" in chips")
cashes.append(float2int(lines[i][pos1:pos2])) cashes.append(float2int(lines[i][pos1:pos2]))
return {'startCashes':cashes, 'seatNos':seatNos} return {'startCashes':cashes, 'seatNos':seatNos}
#end def parseCashesAndSeatNos
#returns the buyin of a tourney in cents #returns the buyin of a tourney in cents
def parseFee(topline): def parseFee(topline):
@ -655,7 +639,6 @@ def parseFee(topline):
pos1=topline.find("$",pos1)+1 pos1=topline.find("$",pos1)+1
pos2=topline.find(" ", pos1) pos2=topline.find(" ", pos1)
return float2int(topline[pos1:pos2]) return float2int(topline[pos1:pos2])
#end def parsefee
#returns a datetime object with the starttime indicated in the given topline #returns a datetime object with the starttime indicated in the given topline
def parseHandStartTime(topline): def parseHandStartTime(topline):
@ -688,10 +671,9 @@ def parseHandStartTime(topline):
result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC'))) result = datetime.datetime(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
if not isUTC: #these use US ET if not isUTC: #these use US ET
result+=datetime.timedelta(hours=5) result += datetime.timedelta(hours=5)
return result return result
#end def parseHandStartTime
#parses the names out of the given lines and returns them as an array #parses the names out of the given lines and returns them as an array
def findName(line): def findName(line):
@ -701,13 +683,11 @@ def findName(line):
def parseNames(lines): def parseNames(lines):
return [findName(line) for line in lines] return [findName(line) for line in lines]
#end def parseNames
def parsePositions(hand, names): def parsePositions(hand, names):
positions = [-1 for i in names] positions = [-1 for i in names]
sb, bb = -1, -1 sb, bb = -1, -1
#find blinds
for line in hand: for line in hand:
if sb == -1 and "small blind" in line and "dead small blind" not in line: if sb == -1 and "small blind" in line and "dead small blind" not in line:
sb = line sb = line
@ -735,10 +715,7 @@ def parsePositions(hand, names):
positions[bb]="B" positions[bb]="B"
#fill up rest of array #fill up rest of array
if sbExists: arraypos = sb - 1 if sbExists else bb - 1
arraypos = sb-1
else:
arraypos = bb-1
distFromBtn=0 distFromBtn=0
while arraypos >= 0 and arraypos != bb: while arraypos >= 0 and arraypos != bb:
@ -753,21 +730,19 @@ def parsePositions(hand, names):
while positions[i] < 0 and i != sb: while positions[i] < 0 and i != sb:
positions[i] = 9 positions[i] = 9
i -= 1 i -= 1
### RHH - Changed to set the null seats before BB to "9" ### RHH - Changed to set the null seats before BB to "9"
if sbExists: i = sb - 1 if sbExists else bb - 1
i = sb-1
else:
i = bb-1
while positions[i] < 0: while positions[i] < 0:
positions[i]=9 positions[i]=9
i-=1 i-=1
arraypos=len(names)-1 arraypos=len(names)-1
if (bb!=0 or (bb==0 and sbExists==False) or (bb == 1 and sb != arraypos) ): if (bb!=0 or (bb==0 and sbExists==False) or (bb == 1 and sb != arraypos) ):
while (arraypos>bb and arraypos > sb): while (arraypos > bb and arraypos > sb):
positions[arraypos]=distFromBtn positions[arraypos] = distFromBtn
arraypos-=1 arraypos -= 1
distFromBtn+=1 distFromBtn += 1
if any(p == -1 for p in positions): if any(p == -1 for p in positions):
print "parsePositions names:",names print "parsePositions names:",names
@ -775,21 +750,18 @@ def parsePositions(hand, names):
raise FpdbError ("failed to read positions") raise FpdbError ("failed to read positions")
# print str(positions), "\n" # print str(positions), "\n"
return positions return positions
#end def parsePositions
#simply parses the rake amount and returns it as an int #simply parses the rake amount and returns it as an int
def parseRake(line): def parseRake(line):
pos=line.find("Rake")+6 pos = line.find("Rake")+6
rake=float2int(line[pos:]) rake = float2int(line[pos:])
return rake return rake
#end def parseRake
def parseSiteHandNo(topline): def parseSiteHandNo(topline):
"""returns the hand no assigned by the poker site""" """returns the hand no assigned by the poker site"""
pos1=topline.find("#")+1 pos1 = topline.find("#")+1
pos2=topline.find(":") pos2 = topline.find(":")
return topline[pos1:pos2] return topline[pos1:pos2]
#end def parseSiteHandNo
def parseTableLine(base, line): def parseTableLine(base, line):
"""returns a dictionary with maxSeats and tableName""" """returns a dictionary with maxSeats and tableName"""
@ -804,11 +776,10 @@ def parseTableLine(base, line):
#returns the hand no assigned by the poker site #returns the hand no assigned by the poker site
def parseTourneyNo(topline): def parseTourneyNo(topline):
pos1=topline.find("Tournament #")+12 pos1 = topline.find("Tournament #")+12
pos2=topline.find(",", pos1) pos2 = topline.find(",", pos1)
#print "parseTourneyNo pos1:",pos1," pos2:",pos2, " result:",topline[pos1:pos2] #print "parseTourneyNo pos1:",pos1," pos2:",pos2, " result:",topline[pos1:pos2]
return topline[pos1:pos2] return topline[pos1:pos2]
#end def parseTourneyNo
#parses a win/collect line. manipulates the passed array winnings, no explicit return #parses a win/collect line. manipulates the passed array winnings, no explicit return
def parseWinLine(line, names, winnings, isTourney): def parseWinLine(line, names, winnings, isTourney):
@ -819,12 +790,11 @@ def parseWinLine(line, names, winnings, isTourney):
if isTourney: if isTourney:
pos1 = line.rfind("collected ") + 10 pos1 = line.rfind("collected ") + 10
pos2 = line.find(" ", pos1) pos2 = line.find(" ", pos1)
winnings[i]+=int(line[pos1:pos2]) winnings[i] += int(line[pos1:pos2])
else: else:
pos1 = line.rfind("$") + 1 pos1 = line.rfind("$") + 1
pos2 = line.find(" ", pos1) pos2 = line.find(" ", pos1)
winnings[i] += float2int(line[pos1:pos2]) winnings[i] += float2int(line[pos1:pos2])
#end def parseWinLine
#returns the category (as per database) string for the given line #returns the category (as per database) string for the given line
def recogniseCategory(line): def recogniseCategory(line):
@ -844,7 +814,6 @@ def recogniseCategory(line):
return "studhilo" return "studhilo"
else: else:
raise FpdbError("failed to recognise category, line:"+line) raise FpdbError("failed to recognise category, line:"+line)
#end def recogniseCategory
#returns the int for the gametype_id for the given line #returns the int for the gametype_id for the given line
def recogniseGametypeID(backend, db, cursor, topline, smallBlindLine, site_id, category, isTourney):#todo: this method is messy def recogniseGametypeID(backend, db, cursor, topline, smallBlindLine, site_id, category, isTourney):#todo: this method is messy
@ -853,50 +822,50 @@ def recogniseGametypeID(backend, db, cursor, topline, smallBlindLine, site_id, c
#note: the below variable names small_bet and big_bet are misleading, in NL/PL they mean small/big blind #note: the below variable names small_bet and big_bet are misleading, in NL/PL they mean small/big blind
if isTourney: if isTourney:
type="tour" type = "tour"
pos1=topline.find("(")+1 pos1 = topline.find("(")+1
if (topline[pos1]=="H" or topline[pos1]=="O" or topline[pos1]=="R" or topline[pos1]=="S" or topline[pos1+2]=="C"): if(topline[pos1] == "H" or topline[pos1] == "O" or
pos1=topline.find("(", pos1)+1 topline[pos1] == "R" or topline[pos1]=="S" or
pos2=topline.find("/", pos1) topline[pos1+2] == "C"):
small_bet=int(topline[pos1:pos2]) pos1 = topline.find("(", pos1)+1
pos2 = topline.find("/", pos1)
small_bet = int(topline[pos1:pos2])
else: else:
type="ring" type = "ring"
pos1=topline.find("$")+1 pos1 = topline.find("$")+1
pos2=topline.find("/$") pos2 = topline.find("/$")
small_bet=float2int(topline[pos1:pos2]) small_bet = float2int(topline[pos1:pos2])
pos1=pos2+2 pos1 = pos2+2
if isTourney: if isTourney:
pos1-=1 pos1 -= 1
pos2=topline.find(")") pos2 = topline.find(")")
if pos2<=pos1: if pos2 <= pos1:
pos2=topline.find(")", pos1) pos2 = topline.find(")", pos1)
if isTourney: if isTourney:
big_bet=int(topline[pos1:pos2]) big_bet = int(topline[pos1:pos2])
else: else:
big_bet=float2int(topline[pos1:pos2]) big_bet = float2int(topline[pos1:pos2])
if (topline.find("No Limit")!=-1): if 'No Limit' in topline:
limit_type="nl" limit_type = "nl" if 'Cap No' not in topline else "cn"
if (topline.find("Cap No")!=-1): elif 'Pot Limit' in topline:
limit_type="cn" limit_type = "pl" if 'Cap Pot' not in topline else "cp"
elif (topline.find("Pot Limit")!=-1):
limit_type="pl"
if (topline.find("Cap Pot")!=-1):
limit_type="cp"
else: else:
limit_type="fl" limit_type = "fl"
#print "recogniseGametypeID small_bet/blind:",small_bet,"big bet/blind:", big_bet,"limit type:",limit_type #print "recogniseGametypeID small_bet/blind:",small_bet,"big bet/blind:", big_bet,"limit type:",limit_type
if (limit_type=="fl"): if limit_type == "fl":
cursor.execute ( db.sql.query['getGametypeFL'] cursor.execute(db.sql.query['getGametypeFL'], (site_id, type, category,
, (site_id, type, category, limit_type, small_bet, big_bet)) limit_type, small_bet,
big_bet))
else: else:
cursor.execute ( db.sql.query['getGametypeNL'] cursor.execute(db.sql.query['getGametypeNL'], (site_id, type, category,
, (site_id, type, category, limit_type, small_bet, big_bet)) limit_type, small_bet,
result=cursor.fetchone() big_bet))
result = cursor.fetchone()
#print "recgt1 result=",result #print "recgt1 result=",result
#ret=result[0] #ret=result[0]
#print "recgt1 ret=",ret #print "recgt1 ret=",ret

17
setup.py Normal file
View File

@ -0,0 +1,17 @@
# setup.py
# Python packaging for fpdb
from distutils.core import setup
setup(name = 'fpdb',
description = 'Free Poker Database',
version = '0.10.999',
author = 'FPDB team',
author_email = 'fpdb-main@lists.sourceforge.net',
packages = ['fpdb'],
package_dir = { 'fpdb' : 'pyfpdb' },
data_files = [
('/usr/share/doc/python-fpdb',
['docs/readme.txt', 'docs/release-notes.txt',
'docs/tabledesign.html', 'THANKS.txt'])]
)