diff --git a/pyfpdb/EverleafToFpdb.py b/pyfpdb/EverleafToFpdb.py index 188e4ade..d0885d92 100755 --- a/pyfpdb/EverleafToFpdb.py +++ b/pyfpdb/EverleafToFpdb.py @@ -71,16 +71,17 @@ class Everleaf(HandHistoryConverter): self.setFileType("text", "cp1252") self.rexx.setGameInfoRegex('.*Blinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+)') self.rexx.setSplitHandRegex('\n\n+') - self.rexx.setHandInfoRegex('.*#(?P[0-9]+)\n.*\nBlinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+) (?P.*) - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+) - (?P
[0-9]+):(?P[0-9]+):(?P[0-9]+)\nTable (?P[ a-zA-Z]+)\nSeat (?P
[ a-zA-Z]+)\nSeat (?P
[- a-zA-Z]+) (\((?P.+)\) )?- \$?(?P[.0-9]+)/\$?(?P[.0-9]+) - (?P[a-zA-Z\' ]+) - (?P.*)') +# self.rexx.setHandInfoRegex('.*#(?P[0-9]+): Table (?P
[ a-zA-Z]+) - \$?(?P[.0-9]+)/\$?(?P[.0-9]+) - (?P.*) - (?P
[0-9]+):(?P[0-9]+) ET - (?P[0-9]+)/(?P[0-9]+)/(?P[0-9]+)Table (?P
[ a-zA-Z]+)\nSeat (?P
[\' \w]+), (?P\d\d \w+ \d\d\d\d \d\d:\d\d (AM|PM))") + # SB BB HID TABLE DAY MON YEAR HR12 MIN AMPM + + self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P.*)\n') + + #Seat 1: .Lucchess ($4.17 in chips) + self.rexx.setPlayerInfoRegex('Seat (?P[0-9]+): (?P.*) \((\$(?P[.0-9]+) in chips)\)') + + #ANTES/BLINDS + #helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). + self.rexx.setPostSbRegex('(?P.*) posts blind \(\$?(?P[.0-9]+)\), ') + self.rexx.setPostBbRegex('\), (?P.*) posts blind \(\$?(?P[.0-9]+)\).') + self.rexx.setPostBothRegex('.*\n(?P.*): posts small \& big blinds \[\$? (?P[.0-9]+)') + self.rexx.setHeroCardsRegex('.*\nDealt\sto\s(?P.*)\s\[ (?P.*) \]') + + #lopllopl checks, Eurolll checks, .Lucchess checks. + self.rexx.setActionStepRegex('(, )?(?P.*?)(?P bets| checks| raises| calls| folds)( \$(?P\d*\.?\d*))?( and is all-in)?') + + #Uchilka shows [ KC,JD ] + self.rexx.setShowdownActionRegex('(?P.*) shows \[ (?P.+) \]') + + # TODO: read SUMMARY correctly for collected pot stuff. + #Uchilka, bets $11.75, collects $23.04, net $11.29 + self.rexx.setCollectPotRegex('(?P.*), bets.+, collects \$(?P\d*\.?\d*), net.* ') + self.rexx.sits_out_re = re.compile('(?P.*) sits out') + self.rexx.compileRegexes() + + def readSupportedGames(self): + pass + + def determineGameType(self): + # Cheating with this regex, only support nlhe at the moment + gametype = ["ring", "hold", "nl"] + + m = self.rexx.hand_info_re.search(self.obs) + gametype = gametype + [m.group('SB')] + gametype = gametype + [m.group('BB')] + + return gametype + + def readHandInfo(self, hand): + m = self.rexx.hand_info_re.search(hand.string) + hand.handid = m.group('HID') + hand.tablename = m.group('TABLE') + #hand.buttonpos = self.rexx.button_re.search(hand.string).group('BUTTONPNAME') +# These work, but the info is already in the Hand class - should be used for tourneys though. +# m.group('SB') +# m.group('BB') +# m.group('GAMETYPE') + +# Believe Everleaf time is GMT/UTC, no transation necessary +# Stars format (Nov 10 2008): 2008/11/07 12:38:49 CET [2008/11/07 7:38:49 ET] +# or : 2008/11/07 12:38:49 ET +# Not getting it in my HH files yet, so using +# 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 = time.strptime(m.group('DATETIME'), "%d %b %Y %I:%M %p") + #hand.starttime = "%d/%02d/%02d %d:%02d:%02d ET" %(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), + #int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC'))) + + def readPlayerStacks(self, hand): + m = self.rexx.player_info_re.finditer(hand.string) + players = [] + for a in m: + hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) + + def markStreets(self, hand): + # PREFLOP = ** Dealing down cards ** + # This re fails if, say, river is missing; then we don't get the ** that starts the river. + #m = re.search('(\*\* Dealing down cards \*\*\n)(?P.*?\n\*\*)?( Dealing Flop \*\* \[ (?P\S\S), (?P\S\S), (?P\S\S) \])?(?P.*?\*\*)?( Dealing Turn \*\* \[ (?P\S\S) \])?(?P.*?\*\*)?( Dealing River \*\* \[ (?P\S\S) \])?(?P.*)', hand.string,re.DOTALL) + + m = re.search(r"PRE-FLOP(?P.+(?=FLOP)|.+(?=SHOWDOWN))" + r"(FLOP (?P\[board cards .+ \].+(?=TURN)|.+(?=SHOWDOWN)))?" + r"(TURN (?P\[board cards .+ \].+(?=RIVER)|.+(?=SHOWDOWN)))?" + r"(RIVER (?P\[board cards .+ \].+(?=SHOWDOWN)))?", hand.string,re.DOTALL) + + hand.addStreets(m) + + + def readCommunityCards(self, hand, street): + self.rexx.board_re = re.compile(r"\[board cards (?P.+) \]") + print hand.streets.group(street) + if street in ('FLOP','TURN','RIVER'): # a list of streets which get dealt community cards (i.e. all but PREFLOP) + m = self.rexx.board_re.search(hand.streets.group(street)) + hand.setCommunityCards(street, m.group('CARDS').split(',')) + + def readBlinds(self, hand): + try: + m = self.rexx.small_blind_re.search(hand.string) + hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) + except: # no small blind + hand.addBlind(None, None, None) + for a in self.rexx.big_blind_re.finditer(hand.string): + hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) + for a in self.rexx.both_blinds_re.finditer(hand.string): + hand.addBlind(a.group('PNAME'), 'small & big blinds', a.group('SBBB')) + + def readHeroCards(self, hand): + m = self.rexx.hero_cards_re.search(hand.string) + if(m == None): + #Not involved in hand + hand.involved = False + else: + hand.hero = m.group('PNAME') + # "2c, qh" -> set(["2c","qc"]) + # Also works with Omaha hands. + cards = m.group('CARDS') + cards = set(cards.split(',')) + hand.addHoleCards(cards, m.group('PNAME')) + + def readAction(self, hand, street): + m = self.rexx.action_re.finditer(hand.streets.group(street)) + for action in m: + if action.group('ATYPE') == ' raises': + hand.addRaiseTo( street, action.group('PNAME'), action.group('BET') ) + elif action.group('ATYPE') == ' calls': + hand.addCall( street, action.group('PNAME'), action.group('BET') ) + elif action.group('ATYPE') == ' bets': + hand.addBet( street, action.group('PNAME'), action.group('BET') ) + elif action.group('ATYPE') == ' folds': + hand.addFold( street, action.group('PNAME')) + elif action.group('ATYPE') == ' checks': + hand.addCheck( street, action.group('PNAME')) + else: + print "DEBUG: unimplemented readAction: %s %s" %(action.group('PNAME'),action.group('ATYPE'),) + #hand.actions[street] += [[action.group('PNAME'), action.group('ATYPE')]] + # TODO: Everleaf does not record uncalled bets. + + def readShowdownActions(self, hand): + for shows in self.rexx.showdown_action_re.finditer(hand.string): + cards = shows.group('CARDS') + cards = set(cards.split(',')) + hand.addShownCards(cards, shows.group('PNAME')) + + def readCollectPot(self,hand): + for m in self.rexx.collect_pot_re.finditer(hand.string): + hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) + + def readShownCards(self,hand): + return + #for m in self.rexx.collect_pot_re.finditer(hand.string): + #if m.group('CARDS') is not None: + #cards = m.group('CARDS') + #cards = set(cards.split(',')) + #hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards) + + + + +if __name__ == "__main__": + c = Configuration.Config() + if len(sys.argv) == 1: + testfile = "regression-test-files/ongame/nlhe/ong NLH handhq_0.txt" + else: + testfile = sys.argv[1] + e = OnGame(c, testfile) + e.processFile() + print str(e) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index e51538ee..8c0ab66e 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -177,6 +177,7 @@ class fpdb_db: self.cursor.execute("INSERT INTO Settings VALUES (118);") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Full Tilt Poker', 'USD');") self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'PokerStars', 'USD');") + self.cursor.execute("INSERT INTO Sites VALUES (DEFAULT, 'Everleaf', 'USD');") self.cursor.execute("INSERT INTO TourneyTypes VALUES (DEFAULT, 1, 0, 0, 0, False);") #end def fillDefaultData diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index 7029027e..75bdc6cb 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -26,6 +26,308 @@ FTP=2 MYSQL_INNODB=2 PGSQL=3 SQLITE=4 +# Data Structures for index and foreign key creation +# drop_code is an int with possible values: 0 - don't drop for bulk import +# 1 - drop during bulk import +# db differences: +# - note that mysql automatically creates indexes on constrained columns when +# foreign keys are created, while postgres does not. Hence the much longer list +# of indexes is required for postgres. +# all primary keys are left on all the time +# +# table column drop_code + +indexes = [ + [ ] # no db with index 0 + , [ ] # no db with index 1 + , [ # indexes for mysql (list index 2) + {'tab':'Players', 'col':'name', 'drop':0} + , {'tab':'Hands', 'col':'siteHandNo', 'drop':0} + , {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0} + ] + , [ # indexes for postgres (list index 3) + {'tab':'Boardcards', 'col':'handId', 'drop':0} + , {'tab':'Gametypes', 'col':'siteId', 'drop':0} + , {'tab':'Hands', 'col':'gametypeId', 'drop':1} + , {'tab':'Hands', 'col':'siteHandNo', 'drop':0} + , {'tab':'HandsActions', 'col':'handplayerId', 'drop':0} + , {'tab':'HandsPlayers', 'col':'handId', 'drop':1} + , {'tab':'HandsPlayers', 'col':'playerId', 'drop':1} + , {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0} + , {'tab':'HudCache', 'col':'gametypeId', 'drop':1} + , {'tab':'HudCache', 'col':'playerId', 'drop':0} + , {'tab':'HudCache', 'col':'tourneyTypeId', 'drop':0} + , {'tab':'Players', 'col':'siteId', 'drop':1} + , {'tab':'Players', 'col':'name', 'drop':0} + , {'tab':'Tourneys', 'col':'tourneyTypeId', 'drop':1} + , {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0} + , {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0} + , {'tab':'TourneysPlayers', 'col':'tourneyId', 'drop':0} + , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} + ] + ] + +foreignKeys = [ + [ ] # no db with index 0 + , [ ] # no db with index 1 + , [ # foreign keys for mysql + {'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1} + , {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1} + , {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1} + , {'fktab':'HandsActions', 'fkcol':'handPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1} + , {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1} + , {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0} + , {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1} + ] + , [ # foreign keys for postgres + {'fktab':'Hands', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1} + , {'fktab':'HandsPlayers', 'fkcol':'handId', 'rtab':'Hands', 'rcol':'id', 'drop':1} + , {'fktab':'HandsPlayers', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':1} + , {'fktab':'HandsActions', 'fkcol':'handPlayerId', 'rtab':'HandsPlayers', 'rcol':'id', 'drop':1} + , {'fktab':'HudCache', 'fkcol':'gametypeId', 'rtab':'Gametypes', 'rcol':'id', 'drop':1} + , {'fktab':'HudCache', 'fkcol':'playerId', 'rtab':'Players', 'rcol':'id', 'drop':0} + , {'fktab':'HudCache', 'fkcol':'tourneyTypeId', 'rtab':'TourneyTypes', 'rcol':'id', 'drop':1} + ] + ] + + +# MySQL Notes: +# "FOREIGN KEY (handId) REFERENCES Hands(id)" - requires index on Hands.id +# - creates index handId on .handId +# alter table t drop foreign key fk +# alter table t add foreign key (fkcol) references tab(rcol) +# alter table t add constraint c foreign key (fkcol) references tab(rcol) +# (fkcol is used for foreigh key name) + +# mysql to list indexes: +# SELECT table_name, index_name, non_unique, column_name +# FROM INFORMATION_SCHEMA.STATISTICS +# WHERE table_name = 'tbl_name' +# AND table_schema = 'db_name' +# ORDER BY table_name, index_name, seq_in_index +# +# ALTER TABLE Tourneys ADD INDEX siteTourneyNo(siteTourneyNo) +# ALTER TABLE tab DROP INDEX idx + +# mysql to list fks: +# SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name +# FROM information_schema.KEY_COLUMN_USAGE +# WHERE REFERENCED_TABLE_SCHEMA = (your schema name here) +# AND REFERENCED_TABLE_NAME is not null +# ORDER BY TABLE_NAME, COLUMN_NAME; + +# this may indicate missing object +# _mysql_exceptions.OperationalError: (1025, "Error on rename of '.\\fpdb\\hands' to '.\\fpdb\\#sql2-7f0-1b' (errno: 152)") + + +# PG notes: + +# To add a foreign key constraint to a table: +# ALTER TABLE tab ADD CONSTRAINT c FOREIGN KEY (col) REFERENCES t2(col2) MATCH FULL; +# ALTER TABLE tab DROP CONSTRAINT zipchk +# +# Note: index names must be unique across a schema +# CREATE INDEX idx ON tab(col) +# DROP INDEX idx + +def prepareBulkImport(fdb): + """Drop some indexes/foreign keys to prepare for bulk import. + Currently keeping the standalone indexes as needed to import quickly""" + # fdb is a fpdb_db object including backend, db, cursor, sql variables + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(0) # allow table/index operations to work + for fk in foreignKeys[fdb.backend]: + if fk['drop'] == 1: + if fdb.backend == MYSQL_INNODB: + fdb.cursor.execute("SELECT constraint_name " + + "FROM information_schema.KEY_COLUMN_USAGE " + + #"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb' + "WHERE 1=1 " + + "AND table_name = %s AND column_name = %s " + + "AND referenced_table_name = %s " + + "AND referenced_column_name = %s ", + (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) + cons = fdb.cursor.fetchone() + print "preparebulk: cons=", cons + if cons: + print "dropping mysql fk", cons[0], fk['fktab'], fk['fkcol'] + try: + fdb.cursor.execute("alter table " + fk['fktab'] + " drop foreign key " + cons[0]) + except: + pass + elif fdb.backend == PGSQL: + print "dropping pg fk", fk['fktab'], fk['fkcol'] + try: + fdb.cursor.execute("alter table " + fk['fktab'] + " drop constraint " + + fk['fktab'] + '_' + fk['fkcol'] + '_fkey') + except: + pass + else: + print "Only MySQL and Postgres supported so far" + return -1 + + for idx in indexes[fdb.backend]: + if idx['drop'] == 1: + if fdb.backend == MYSQL_INNODB: + print "dropping mysql index ", idx['tab'], idx['col'] + try: + fdb.cursor.execute( "alter table %s drop index %s", (idx['tab'],idx['col']) ) + except: + pass + elif fdb.backend == PGSQL: + print "dropping pg index ", idx['tab'], idx['col'] + # mod to use tab_col for index name? + try: + fdb.cursor.execute( "drop index %s_%s_idx" % (idx['tab'],idx['col']) ) + except: + pass + else: + print "Only MySQL and Postgres supported so far" + return -1 + + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(1) # go back to normal isolation level + fdb.db.commit() # seems to clear up errors if there were any in postgres +#end def prepareBulkImport + +def afterBulkImport(fdb): + """Re-create any dropped indexes/foreign keys after bulk import""" + # fdb is a fpdb_db object including backend, db, cursor, sql variables + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(0) # allow table/index operations to work + for fk in foreignKeys[fdb.backend]: + if fk['drop'] == 1: + if fdb.backend == MYSQL_INNODB: + fdb.cursor.execute("SELECT constraint_name " + + "FROM information_schema.KEY_COLUMN_USAGE " + + #"WHERE REFERENCED_TABLE_SCHEMA = 'fpdb' + "WHERE 1=1 " + + "AND table_name = %s AND column_name = %s " + + "AND referenced_table_name = %s " + + "AND referenced_column_name = %s ", + (fk['fktab'], fk['fkcol'], fk['rtab'], fk['rcol']) ) + cons = fdb.cursor.fetchone() + print "afterbulk: cons=", cons + if cons: + pass + else: + print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol'] + try: + fdb.cursor.execute("alter table " + fk['fktab'] + " add foreign key (" + + fk['fkcol'] + ") references " + fk['rtab'] + "(" + + fk['rcol'] + ")") + except: + pass + elif fdb.backend == PGSQL: + print "creating fk ", fk['fktab'], fk['fkcol'], "->", fk['rtab'], fk['rcol'] + try: + fdb.cursor.execute("alter table " + fk['fktab'] + " add constraint " + + fk['fktab'] + '_' + fk['fkcol'] + '_fkey' + + " foreign key (" + fk['fkcol'] + + ") references " + fk['rtab'] + "(" + fk['rcol'] + ")") + except: + pass + else: + print "Only MySQL and Postgres supported so far" + return -1 + + for idx in indexes[fdb.backend]: + if idx['drop'] == 1: + if fdb.backend == MYSQL_INNODB: + print "creating mysql index ", idx['tab'], idx['col'] + try: + fdb.cursor.execute( "alter table %s add index %s(%s)" + , (idx['tab'],idx['col'],idx['col']) ) + except: + pass + elif fdb.backend == PGSQL: + # mod to use tab_col for index name? + print "creating pg index ", idx['tab'], idx['col'] + try: + print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col']) + fdb.cursor.execute( "create index %s_%s_idx on %s(%s)" + % (idx['tab'], idx['col'], idx['tab'], idx['col']) ) + except: + print " ERROR! :-(" + pass + else: + print "Only MySQL and Postgres supported so far" + return -1 + + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(1) # go back to normal isolation level + fdb.db.commit() # seems to clear up errors if there were any in postgres +#end def afterBulkImport + +def createAllIndexes(fdb): + """Create new indexes""" + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(0) # allow table/index operations to work + for idx in indexes[fdb.backend]: + if fdb.backend == MYSQL_INNODB: + print "creating mysql index ", idx['tab'], idx['col'] + try: + fdb.cursor.execute( "alter table %s add index %s(%s)" + , (idx['tab'],idx['col'],idx['col']) ) + except: + pass + elif fdb.backend == PGSQL: + # mod to use tab_col for index name? + print "creating pg index ", idx['tab'], idx['col'] + try: + print "create index %s_%s_idx on %s(%s)" % (idx['tab'], idx['col'], idx['tab'], idx['col']) + fdb.cursor.execute( "create index %s_%s_idx on %s(%s)" + % (idx['tab'], idx['col'], idx['tab'], idx['col']) ) + except: + print " ERROR! :-(" + pass + else: + print "Only MySQL and Postgres supported so far" + return -1 + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(1) # go back to normal isolation level +#end def createAllIndexes + +def dropAllIndexes(fdb): + """Drop all standalone indexes (i.e. not including primary keys or foreign keys) + using list of indexes in indexes data structure""" + # maybe upgrade to use data dictionary?? (but take care to exclude PK and FK) + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(0) # allow table/index operations to work + for idx in indexes[fdb.backend]: + if fdb.backend == MYSQL_INNODB: + print "dropping mysql index ", idx['tab'], idx['col'] + try: + fdb.cursor.execute( "alter table %s drop index %s" + , (idx['tab'],idx['col']) ) + except: + pass + elif fdb.backend == PGSQL: + print "dropping pg index ", idx['tab'], idx['col'] + # mod to use tab_col for index name? + try: + fdb.cursor.execute( "drop index %s_%s_idx" + % (idx['tab'],idx['col']) ) + except: + pass + else: + print "Only MySQL and Postgres supported so far" + return -1 + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(1) # go back to normal isolation level +#end def dropAllIndexes + +def analyzeDB(fdb): + """Do whatever the DB can offer to update index/table statistics""" + if fdb.backend == PGSQL: + fdb.db.set_isolation_level(0) # allow vacuum to work + try: + fdb.cursor.execute("vacuum analyze") + except: + print "Error during vacuum" + fdb.db.set_isolation_level(1) # go back to normal isolation level +#end def analyzeDB + # Data Structures for index and foreign key creation # drop_code is an int with possible values: 0 - don't drop for bulk import @@ -2542,4 +2844,4 @@ def store_tourneys_players(cursor, tourney_id, player_ids, payin_amounts, ranks, #print "created new tourneys_players.id:",tmp result.append(tmp[0]) return result -#end def store_tourneys_players \ No newline at end of file +#end def store_tourneys_players diff --git a/pyfpdb/psnlheparser-mct.tgz b/pyfpdb/psnlheparser-mct.tgz deleted file mode 100644 index b04c8018..00000000 Binary files a/pyfpdb/psnlheparser-mct.tgz and /dev/null differ