From 4636e244ea99eff81248afb36aa5c39ea58bfcb3 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Sun, 31 Jan 2010 17:58:48 +0100 Subject: [PATCH 01/50] Codec errors seem to lock up HUD with windows. I've made a trap for each error that I observed. don't want to halt the program so I return the original string. The de/recoding is not needed for >95% of the playernames anyway. --- pyfpdb/Charset.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pyfpdb/Charset.py b/pyfpdb/Charset.py index 6b5e6b7c..02b5c098 100644 --- a/pyfpdb/Charset.py +++ b/pyfpdb/Charset.py @@ -41,7 +41,13 @@ def to_utf8(s): return _out except UnicodeDecodeError: sys.stderr.write('Could not convert: "%s"\n' % s) - raise + return s + except UnicodeEncodeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + return s + except TypeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + return s def to_db_utf8(s): if not_needed2: return s @@ -51,7 +57,13 @@ def to_db_utf8(s): return _out except UnicodeDecodeError: sys.stderr.write('Could not convert: "%s"\n' % s) - raise + return s + except UnicodeEncodeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + return s + except TypeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + return s def to_gui(s): if not_needed3: return s @@ -61,5 +73,11 @@ def to_gui(s): return _out except UnicodeDecodeError: sys.stderr.write('Could not convert: "%s"\n' % s) - raise + return s + except UnicodeEncodeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + return s + except TypeError: + sys.stderr.write('Could not convert: "%s"\n' % s) + return s From 0db3cecf650c65ad4cd57a421a5cc91e83c18f89 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Sat, 13 Feb 2010 20:41:01 +0100 Subject: [PATCH 02/50] Action Reg_ex updated Due to the added end of line marker to eliminate playersnames that start with card, all-in actions are no longer supported. --- pyfpdb/PokerStarsToFpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 90a8bfd0..39eb0892 100755 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -102,7 +102,7 @@ class PokerStars(HandHistoryConverter): self.re_HeroCards = re.compile(r"^Dealt to %(PLYR)s(?: \[(?P.+?)\])?( \[(?P.+?)\])" % subst, re.MULTILINE) self.re_Action = re.compile(r""" ^%(PLYR)s:(?P\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat) - (\s(%(CUR)s)?(?P[.\d]+))?(\sto\s%(CUR)s(?P[.\d]+))? # the number discarded goes in + (\s(%(CUR)s)?(?P[.\d]+))?(\sto\s%(CUR)s(?P[.\d]+))?(\sand\sis\sall-in)? # the number discarded goes in (\scards?(\s\[(?P.+?)\])?)?$""" % subst, re.MULTILINE|re.VERBOSE) self.re_ShowdownAction = re.compile(r"^%s: shows \[(?P.*)\]" % player_re, re.MULTILINE) From 466988ea4acc1736358bcdca234ee56c5478b111 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Sat, 13 Feb 2010 22:44:00 +0100 Subject: [PATCH 03/50] Tournement support for PartyPoker --- pyfpdb/PartyPokerToFpdb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyfpdb/PartyPokerToFpdb.py b/pyfpdb/PartyPokerToFpdb.py index 1d1d0e4d..5fdeae0c 100755 --- a/pyfpdb/PartyPokerToFpdb.py +++ b/pyfpdb/PartyPokerToFpdb.py @@ -465,8 +465,9 @@ class PartyPoker(HandHistoryConverter): def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): "Returns string to search in windows titles" if type=="tour": - print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (table_name, table_number) - return "%s.+Table\s#%s" % (table_name, table_number) + TableName = table_name.split(" ") + print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (TableName[0], table_number) + return "%s.+Table\s#%s" % (TableName[0], table_number) else: print 'party', 'getTableTitleRe', table_number return table_name From 074a4e751eb203001984dc499b0b1fbc52619a58 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 17 Feb 2010 19:25:04 +0000 Subject: [PATCH 04/50] make sure filter releases any db locks it has --- pyfpdb/Filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpdb/Filters.py b/pyfpdb/Filters.py index 27e5a49d..c8e8e219 100644 --- a/pyfpdb/Filters.py +++ b/pyfpdb/Filters.py @@ -211,6 +211,9 @@ class Filters(threading.Thread): self.Button2.connect("clicked", self.callback['button2'], "clicked") self.Button2.set_sensitive(True) + # make sure any locks on db are released: + self.db.rollback() + def get_vbox(self): """returns the vbox of this thread""" return self.mainVBox From 55b6e1ee928b0ef76b3987fe41578935b4e8e638 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 17 Feb 2010 21:18:38 +0000 Subject: [PATCH 05/50] change button text --- pyfpdb/GuiPlayerStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiPlayerStats.py b/pyfpdb/GuiPlayerStats.py index f7789164..047c1ff1 100644 --- a/pyfpdb/GuiPlayerStats.py +++ b/pyfpdb/GuiPlayerStats.py @@ -81,7 +81,7 @@ class GuiPlayerStats (threading.Thread): self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display) self.filters.registerButton1Name("_Filters") self.filters.registerButton1Callback(self.showDetailFilter) - self.filters.registerButton2Name("_Refresh") + self.filters.registerButton2Name("_Refresh Stats") self.filters.registerButton2Callback(self.refreshStats) # ToDo: store in config From c31a2f6cefd2c8e8ba01686f435305c281f0b559 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 17 Feb 2010 21:44:05 +0000 Subject: [PATCH 06/50] undo earlier 're-fix' that broke things --- pyfpdb/DerivedStats.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index d7ded83b..699ad068 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -227,7 +227,7 @@ class DerivedStats(): #print "bb =", bb, "sb =", sb, "players =", players for i,player in enumerate(reversed(players)): - self.handsplayers[player]['position'] = str(i) + self.handsplayers[player]['position'] = i def assembleHudCache(self, hand): # No real work to be done - HandsPlayers data already contains the correct info @@ -305,12 +305,13 @@ class DerivedStats(): """Fills stealAttempt(Chance|ed, fold(Bb|Sb)ToSteal(Chance|) Steal attempt - open raise on positions 1 0 S - i.e. MP3, CO, BU, SB + (note: I don't think PT2 counts SB steals in HU hands, maybe we shouldn't?) Fold to steal - folding blind after steal attemp wo any other callers or raisers """ steal_attempt = False - steal_positions = ('1', '0', 'S') + steal_positions = (1, 0, 'S') if hand.gametype['base'] == 'stud': - steal_positions = ('2', '1', '0') + steal_positions = (2, 1, '0') for action in hand.actions[hand.actionStreets[1]]: pname, act = action[0], action[1] posn = self.handsplayers[pname]['position'] From 0cafb75c59af0a6380b32907fb9ede41bc34d66f Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 17 Feb 2010 23:29:50 +0000 Subject: [PATCH 07/50] finish previous undo --- pyfpdb/DerivedStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 699ad068..dddbec8b 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -311,7 +311,7 @@ class DerivedStats(): steal_attempt = False steal_positions = (1, 0, 'S') if hand.gametype['base'] == 'stud': - steal_positions = (2, 1, '0') + steal_positions = (2, 1, 0) for action in hand.actions[hand.actionStreets[1]]: pname, act = action[0], action[1] posn = self.handsplayers[pname]['position'] From a27bc45f6dbc2bb954f98c991ae86a7458ef7bbf Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Thu, 18 Feb 2010 16:17:08 +0100 Subject: [PATCH 08/50] fix for dead blinds and antes in the player stat calculation Posted dead blinds messed up the rake and profit calculation in the player stats. This fix should also work for antes The fix has been done for omaha and holdem game types (without antes) I don't have draw or stud hands to check this --- pyfpdb/DerivedStats.py | 2 +- pyfpdb/Hand.py | 36 ++++++++++++++++++++---------------- pyfpdb/PokerStarsToFpdb.py | 18 ++++++++---------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 166034a5..32c7c874 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -162,7 +162,7 @@ class DerivedStats(): self.handsplayers[player]['wonAtSD'] = 1.0 for player in hand.pot.committed: - self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) + self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])- (100*hand.pot.common[player])) self.calcCBets(hand) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 73dd7600..c5e2dea7 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -321,8 +321,10 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio self.stacks[player] -= Decimal(ante) act = (player, 'posts', "ante", ante, self.stacks[player]==0) self.actions['BLINDSANTES'].append(act) - self.pot.addMoney(player, Decimal(ante)) - +# self.pot.addMoney(player, Decimal(ante)) + self.pot.addCommonMoney(player, Decimal(ante)) +#I think the antes should be common money, don't have enough hand history to check + def addBlind(self, player, blindtype, amount): # if player is None, it's a missing small blind. # The situation we need to cover are: @@ -340,9 +342,12 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio self.actions['BLINDSANTES'].append(act) if blindtype == 'both': - amount = self.bb - self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) - self.pot.addCommonMoney(Decimal(self.sb)) + # work with the real ammount. limit games are listed as $1, $2, where + # the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2.... + amount = Decimal(amount)/3 + self.bets['BLINDSANTES'][player].append(amount) + self.pot.addCommonMoney(player, amount) + amount += amount self.bets['PREFLOP'][player].append(Decimal(amount)) self.pot.addMoney(player, Decimal(amount)) @@ -504,10 +509,7 @@ Card ranks will be uppercased self.totalcollected = 0; #self.collected looks like [[p1,amount][px,amount]] for entry in self.collected: - self.totalcollected += Decimal(entry[1]) - - - + self.totalcollected += Decimal(entry[1]) def getGameTypeAsString(self): """\ @@ -986,11 +988,12 @@ class DrawHand(Hand): self.lastBet['DEAL'] = Decimal(amount) elif blindtype == 'both': # extra small blind is 'dead' - self.lastBet['DEAL'] = Decimal(self.bb) + amount = Decimal(amount)/3 + amount += amount + self.lastBet['DEAL'] = Decimal(amount) self.posted = self.posted + [[player,blindtype]] #print "DEBUG: self.posted: %s" %(self.posted) - def addShownCards(self, cards, player, shown=True, mucked=False, dealt=False): if player == self.hero: # we have hero's cards just update shown/mucked if shown: self.shown.add(player) @@ -1405,7 +1408,7 @@ class Pot(object): self.contenders = set() self.committed = {} self.streettotals = {} - self.common = Decimal(0) + self.common = {} self.total = None self.returned = {} self.sym = u'$' # this is the default currency symbol @@ -1415,13 +1418,14 @@ class Pot(object): def addPlayer(self,player): self.committed[player] = Decimal(0) + self.common[player] = Decimal(0) def addFold(self, player): # addFold must be called when a player folds self.contenders.discard(player) - def addCommonMoney(self, amount): - self.common += amount + def addCommonMoney(self, player, amount): + self.common[player] += amount def addMoney(self, player, amount): # addMoney must be called for any actions that put money in the pot, in the order they occur @@ -1429,7 +1433,7 @@ class Pot(object): self.committed[player] += amount def markTotal(self, street): - self.streettotals[street] = sum(self.committed.values()) + self.common + self.streettotals[street] = sum(self.committed.values()) + sum(self.common.values()) def getTotalAtStreet(self, street): if street in self.streettotals: @@ -1437,7 +1441,7 @@ class Pot(object): return 0 def end(self): - self.total = sum(self.committed.values()) + self.common + self.total = sum(self.committed.values()) + sum(self.common.values()) # Return any uncalled bet. committed = sorted([ (v,k) for (k,v) in self.committed.items()]) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 39066b8e..6874c708 100644 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -287,16 +287,14 @@ class PokerStars(HandHistoryConverter): hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) def readBlinds(self, hand): - try: - count = 0 - for a in self.re_PostSB.finditer(hand.handText): - if count == 0: - hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB')) - count = 1 - else: - hand.addAnte(a.group('PNAME'), a.group('SB')) - except: # no small blind - hand.addBlind(None, None, None) + liveBlind = True + for a in self.re_PostSB.finditer(hand.handText): + if liveBlind: + hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB')) + liveBlind = False + else: + # Post dead blinds as ante + hand.addAnte(a.group('PNAME'), a.group('SB')) for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) for a in self.re_PostBoth.finditer(hand.handText): From 35a604dcc6094fd4cedb6a53e0100ef29d09b244 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Thu, 18 Feb 2010 16:39:51 +0100 Subject: [PATCH 09/50] Add support for posting dead small blind --- pyfpdb/FulltiltToFpdb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index af55fd41..4ee12fa4 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -128,6 +128,7 @@ class Fulltilt(HandHistoryConverter): player_re = "(?P" + "|".join(map(re.escape, players)) + ")" logging.debug("player_re: " + player_re) self.re_PostSB = re.compile(r"^%s posts the small blind of \$?(?P[.0-9]+)" % player_re, re.MULTILINE) + self.re_PostDead = re.compile(r"^%s posts a dead small blind of \$?(?P[.0-9]+)" % player_re, re.MULTILINE) self.re_PostBB = re.compile(r"^%s posts (the big blind of )?\$?(?P[.0-9]+)" % player_re, re.MULTILINE) self.re_Antes = re.compile(r"^%s antes \$?(?P[.0-9]+)" % player_re, re.MULTILINE) self.re_BringIn = re.compile(r"^%s brings in for \$?(?P[.0-9]+)" % player_re, re.MULTILINE) @@ -298,6 +299,8 @@ class Fulltilt(HandHistoryConverter): hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) except: # no small blind hand.addBlind(None, None, None) + for a in self.re_PostDead.finditer(hand.handText): + hand.addAnte(a.group('PNAME'), a.group('SB')) for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) for a in self.re_PostBoth.finditer(hand.handText): From 97ec91c165c79329406b8748911493ba043cf04a Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Thu, 18 Feb 2010 16:00:12 -0500 Subject: [PATCH 10/50] Revert fix removing the creation of folders for converted HHs. --- pyfpdb/HandHistoryConverter.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 4bf7564d..41d5b39a 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -92,7 +92,23 @@ follow : whether to tail -f the input""" self.out_fh = sys.stdout else: # TODO: out_path should be sanity checked. - self.out_fh = sys.stdout + out_dir = os.path.dirname(self.out_path) + if not os.path.isdir(out_dir) and out_dir != '': + try: + os.makedirs(out_dir) + except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D + log.error("Unable to create output directory %s for HHC!" % out_dir) + print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir + # TODO: pop up a box to allow person to choose output directory? + # TODO: shouldn't that be done when we startup, actually? + else: + log.info("Created directory '%s'" % out_dir) + try: + self.out_fh = codecs.open(self.out_path, 'w', 'utf8') + except: + log.error("out_path %s couldn't be opened" % (self.out_path)) + else: + log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh)) self.follow = follow self.compiledPlayers = set() self.maxseats = 10 From 6272c057b93fadf675159414e31d4db7ea0739b4 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Thu, 18 Feb 2010 22:12:01 +0100 Subject: [PATCH 11/50] Pokerstar Big and Small blind for limit games now derived form lookup table. This also needs to be done for PokerStars, don't know for the other sites.... --- pyfpdb/Hand.py | 11 +++++------ pyfpdb/PokerStarsToFpdb.py | 30 ++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 262ac4ad..b740a341 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -344,15 +344,14 @@ For sites (currently only Carbon Poker) which record "all in" as a special actio if blindtype == 'both': # work with the real ammount. limit games are listed as $1, $2, where # the SB 0.50 and the BB is $1, after the turn the minimum bet amount is $2.... - amount = Decimal(amount)/3 - self.bets['BLINDSANTES'][player].append(amount) - self.pot.addCommonMoney(player, amount) - amount += amount + amount = self.bb + self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) + self.pot.addCommonMoney(player, Decimal(self.sb)) if blindtype == 'secondsb': amount = Decimal(0) self.bets['BLINDSANTES'][player].append(Decimal(self.sb)) - self.pot.addCommonMoney(Decimal(self.sb)) + self.pot.addCommonMoney(player, Decimal(self.sb)) self.bets['PREFLOP'][player].append(Decimal(amount)) self.pot.addMoney(player, Decimal(amount)) @@ -1450,7 +1449,7 @@ class Pot(object): # Return any uncalled bet. committed = sorted([ (v,k) for (k,v) in self.committed.items()]) - print "DEBUG: committed: %s" % committed + #print "DEBUG: committed: %s" % committed #ERROR below. lastbet is correct in most cases, but wrong when # additional money is committed to the pot in cash games # due to an additional sb being posted. (Speculate that diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 87574e9d..8c222e12 100644 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -140,6 +140,14 @@ class PokerStars(HandHistoryConverter): mg = m.groupdict() # translations from captured groups to fpdb info strings + Lim_Blinds = { '0.04': ('0.01', '0.02'), '0.10': ('0.02', '0.05'), '0.20': ('0.05', '0.10'), + '0.50': ('0.10', '0.25'), '1.00': ('0.25', '0.50'), '2.00': ('0.50', '1.00'), + '4.00': ('1.00', '2.00'), '6.00': ('1.00', '3.00'), '10.00': ('2.00', '5.00'), + '20.00': ('5.00', '10.00'), '30.00': ('10.00', '15.00'), '60.00': ('15.00', '30.00'), + '100.00': ('25.00', '50.00'),'200.00': ('50.00', '100.00'),'400.00': ('100.00', '200.00'), + '1000.00': ('250.00', '500.00')} + + limits = { 'No Limit':'nl', 'Pot Limit':'pl', 'Limit':'fl' } games = { # base, category "Hold'em" : ('hold','holdem'), @@ -173,6 +181,10 @@ class PokerStars(HandHistoryConverter): else: info['type'] = 'tour' + if info['limitType'] == 'fl' and info['bb'] != None: + info['sb'] = Lim_Blinds[mg['BB']][0] + info['bb'] = Lim_Blinds[mg['BB']][1] + # NB: SB, BB must be interpreted as blinds or bets depending on limit type. return info @@ -287,16 +299,14 @@ class PokerStars(HandHistoryConverter): hand.addBringIn(m.group('PNAME'), m.group('BRINGIN')) def readBlinds(self, hand): - try: - count = 0 - for a in self.re_PostSB.finditer(hand.handText): - if count == 0: - hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB')) - count = 1 - else: - hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB')) - except: # no small blind - hand.addBlind(None, None, None) + liveBlind = True + for a in self.re_PostSB.finditer(hand.handText): + if liveBlind: + hand.addBlind(a.group('PNAME'), 'small blind', a.group('SB')) + liveBlind = False + else: + # Post dead blinds as ante + hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB')) for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) for a in self.re_PostBoth.finditer(hand.handText): From aceb94d26055cf05bd7fdbbda74eb9bb712d5489 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Thu, 18 Feb 2010 22:24:25 +0100 Subject: [PATCH 12/50] Oops... Lookup is only for ring games.... --- pyfpdb/PokerStarsToFpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 8c222e12..946d5ea2 100644 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -181,7 +181,7 @@ class PokerStars(HandHistoryConverter): else: info['type'] = 'tour' - if info['limitType'] == 'fl' and info['bb'] != None: + if info['limitType'] == 'fl' and info['bb'] != None and info['type'] == 'ring': info['sb'] = Lim_Blinds[mg['BB']][0] info['bb'] = Lim_Blinds[mg['BB']][1] From af6dbc8d5fffda420f27ea9a37a58f2dcbfb95c6 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Thu, 18 Feb 2010 22:32:53 +0100 Subject: [PATCH 13/50] Copied same dead blind procedure from Pokerstars to Full Tilt --- pyfpdb/FulltiltToFpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index 4ee12fa4..2a3050ab 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -300,7 +300,7 @@ class Fulltilt(HandHistoryConverter): except: # no small blind hand.addBlind(None, None, None) for a in self.re_PostDead.finditer(hand.handText): - hand.addAnte(a.group('PNAME'), a.group('SB')) + hand.addBlind(a.group('PNAME'), 'secondsb', a.group('SB')) for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) for a in self.re_PostBoth.finditer(hand.handText): From c33575911d12e6e9af5e383103eeac2ba68ff1af Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Fri, 19 Feb 2010 17:50:45 -0500 Subject: [PATCH 14/50] Don't make folders or files for Stars format HHs. --- pyfpdb/HandHistoryConverter.py | 48 +++++++++++++++++++--------------- pyfpdb/fpdb.py | 35 +++++++++++++------------ 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 41d5b39a..0edaaed0 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -69,6 +69,7 @@ out_path (default '-' = sys.stdout) follow : whether to tail -f the input""" self.config = config + self.import_parameters = self.config.get_import_parameters() #log = Configuration.get_logger("logging.conf", "parser", log_dir=self.config.dir_log) log.info("HandHistory init - %s subclass, in_path '%s'; out_path '%s'" % (self.sitename, in_path, out_path) ) @@ -87,28 +88,8 @@ follow : whether to tail -f the input""" if in_path == '-': self.in_fh = sys.stdin + self.out_fh = get_out_fh(out_path, self.import_parameters) - if out_path == '-': - self.out_fh = sys.stdout - else: - # TODO: out_path should be sanity checked. - out_dir = os.path.dirname(self.out_path) - if not os.path.isdir(out_dir) and out_dir != '': - try: - os.makedirs(out_dir) - except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D - log.error("Unable to create output directory %s for HHC!" % out_dir) - print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir - # TODO: pop up a box to allow person to choose output directory? - # TODO: shouldn't that be done when we startup, actually? - else: - log.info("Created directory '%s'" % out_dir) - try: - self.out_fh = codecs.open(self.out_path, 'w', 'utf8') - except: - log.error("out_path %s couldn't be opened" % (self.out_path)) - else: - log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh)) self.follow = follow self.compiledPlayers = set() self.maxseats = 10 @@ -531,3 +512,28 @@ def getSiteHhc(config, sitename): hhcName = config.supported_sites[sitename].converter hhcModule = __import__(hhcName) return getattr(hhcModule, hhcName[:-6]) + +def get_out_fh(out_path, parameters): + if out_path == '-': + return(sys.stdout) + elif parameters['saveStarsHH']: + # TODO: out_path should be sanity checked. + out_dir = os.path.dirname(self.out_path) + if not os.path.isdir(out_dir) and out_dir != '': + try: + os.makedirs(out_dir) + except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D + log.error("Unable to create output directory %s for HHC!" % out_dir) + print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir + # TODO: pop up a box to allow person to choose output directory? + # TODO: shouldn't that be done when we startup, actually? + else: + log.info("Created directory '%s'" % out_dir) + try: + return(codecs.open(self.out_path, 'w', 'utf8')) + except: + log.error("out_path %s couldn't be opened" % (self.out_path)) + else: + log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh)) + else: + return(sys.stdout) \ No newline at end of file diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 55b19a3b..0abffcd0 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -997,23 +997,24 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") return response def validate_config(self): - hhbase = self.config.get_import_parameters().get("hhArchiveBase") - hhbase = os.path.expanduser(hhbase) - #hhdir = os.path.join(hhbase,site) - hhdir = hhbase - if not os.path.isdir(hhdir): - diapath = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Setup hh dir") - diastring = "WARNING: Unable to find output hh directory %s\n\n Press YES to create this directory, or NO to select a new one." % hhdir - diapath.format_secondary_text(diastring) - response = diapath.run() - diapath.destroy() - if response == gtk.RESPONSE_YES: - try: - os.makedirs(hhdir) - except: - self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.") - elif response == gtk.RESPONSE_NO: - self.select_hhArchiveBase() + if self.config.get_import_parameters().get('saveStarsHH'): + hhbase = self.config.get_import_parameters().get("hhArchiveBase") + hhbase = os.path.expanduser(hhbase) + #hhdir = os.path.join(hhbase,site) + hhdir = hhbase + if not os.path.isdir(hhdir): + diapath = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Setup hh dir") + diastring = "WARNING: Unable to find output hh directory %s\n\n Press YES to create this directory, or NO to select a new one." % hhdir + diapath.format_secondary_text(diastring) + response = diapath.run() + diapath.destroy() + if response == gtk.RESPONSE_YES: + try: + os.makedirs(hhdir) + except: + self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.") + elif response == gtk.RESPONSE_NO: + self.select_hhArchiveBase() def select_hhArchiveBase(self, widget=None): fc = gtk.FileChooserDialog(title="Select HH Output Directory", parent=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_OPEN,gtk.RESPONSE_OK), backend=None) From a9ec972ba595022535377aeff3015dcb8df049cd Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Fri, 19 Feb 2010 20:14:34 -0500 Subject: [PATCH 15/50] Clean some pylint errors and obsolete TODOs. --- pyfpdb/HandHistoryConverter.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index 0edaaed0..2053eba9 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -443,8 +443,8 @@ or None if we fail to get the info """ def guessMaxSeats(self, hand): """Return a guess at maxseats when not specified in HH.""" # if some other code prior to this has already set it, return it - if maxseats > 1 and maxseats < 11: - return maxseats + if self.maxseats > 1 and self.maxseats < 11: + return self.maxseats mo = self.maxOccSeat(hand) if mo == 10: return 10 #that was easy @@ -517,23 +517,18 @@ def get_out_fh(out_path, parameters): if out_path == '-': return(sys.stdout) elif parameters['saveStarsHH']: - # TODO: out_path should be sanity checked. - out_dir = os.path.dirname(self.out_path) + out_dir = os.path.dirname(out_path) if not os.path.isdir(out_dir) and out_dir != '': try: os.makedirs(out_dir) except: # we get a WindowsError here in Windows.. pretty sure something else for Linux :D log.error("Unable to create output directory %s for HHC!" % out_dir) print "*** ERROR: UNABLE TO CREATE OUTPUT DIRECTORY", out_dir - # TODO: pop up a box to allow person to choose output directory? - # TODO: shouldn't that be done when we startup, actually? else: log.info("Created directory '%s'" % out_dir) try: - return(codecs.open(self.out_path, 'w', 'utf8')) + return(codecs.open(out_path, 'w', 'utf8')) except: - log.error("out_path %s couldn't be opened" % (self.out_path)) - else: - log.debug("out_path %s opened as %s" % (self.out_path, self.out_fh)) + log.error("out_path %s couldn't be opened" % (out_path)) else: - return(sys.stdout) \ No newline at end of file + return(sys.stdout) From ebf2205859ef4a0ea55cad0ab1cbaeca04c8e2c0 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sat, 20 Feb 2010 10:27:58 +0000 Subject: [PATCH 16/50] display popup if error parsing config file --- pyfpdb/Configuration.py | 13 ++++++++++--- pyfpdb/fpdb.py | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index f7694e90..3d506735 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -481,12 +481,19 @@ class Config: print "\nReading configuration file %s\n" % file try: doc = xml.dom.minidom.parse(file) + self.file_error = None except: log.error("Error parsing %s. See error log file." % (file)) traceback.print_exc(file=sys.stderr) - print "press enter to continue" - sys.stdin.readline() - sys.exit() + self.file_error = sys.exc_info()[1] + # we could add a parameter to decide whether to return or read a line and exit? + return + #print "press enter to continue" + #sys.stdin.readline() + #sys.exit() +#ExpatError: not well-formed (invalid token): line 511, column 4 +#sys.exc_info = (, ExpatError('not well-formed (invalid token): line 511, +# column 4',), ) self.doc = doc self.supported_sites = {} diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 55b19a3b..29f5ff6a 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -688,6 +688,12 @@ class fpdb: def load_profile(self): """Loads profile from the provided path name.""" self.config = Configuration.Config(file=options.config, dbname=options.dbname) + if self.config.file_error: + self.warning_box( "There is an error in your config file\n" + self.config.file + + "\n\nError is: " + str(self.config.file_error) + , diatitle="CONFIG FILE ERROR" ) + exit() + log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log) print "Logfile is " + os.path.join(self.config.dir_log, self.config.log_file) + "\n" if self.config.example_copy: From ae59fa715b6bd56cfd65a52c7f281e46630ce410 Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Sat, 20 Feb 2010 11:24:07 -0500 Subject: [PATCH 17/50] Correctly pass dbname to config. Clean some pylint complaints. --- pyfpdb/HUD_main.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 2ea223f4..92f888d8 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -61,7 +61,7 @@ import Hud # get config and set up logger -c = Configuration.Config(file=options.config) +c = Configuration.Config(file=options.config, dbname=options.dbname) log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt') @@ -78,14 +78,14 @@ class HUD_main(object): try: if not options.errorsToConsole: - fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt') - print "Note: error output is being diverted to:\n"+fileName \ - + "\nAny major error will be reported there _only_.\n" - log.info("Note: error output is being diverted to:"+fileName) - log.info("Any major error will be reported there _only_.") - errorFile = open(fileName, 'w', 0) - sys.stderr = errorFile - sys.stderr.write("HUD_main: starting ...\n") + fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt') + print "Note: error output is being diverted to:\n"+fileName \ + + "\nAny major error will be reported there _only_.\n" + log.info("Note: error output is being diverted to:"+fileName) + log.info("Any major error will be reported there _only_.") + errorFile = open(fileName, 'w', 0) + sys.stderr = errorFile + sys.stderr.write("HUD_main: starting ...\n") self.hud_dict = {} self.hud_params = self.config.get_hud_ui_parameters() @@ -237,7 +237,7 @@ class HUD_main(object): try: (table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \ self.db_connection.get_table_info(new_hand_id) - except Exception, err: + except Exception: log.error("db error: skipping %s" % new_hand_id) continue t1 = time.time() From 21396e101ef4eadb1e90cf9dc759d231deacaaba Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Sat, 20 Feb 2010 11:55:52 -0500 Subject: [PATCH 18/50] Create saveStarsHH option in import. --- pyfpdb/Configuration.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index f7694e90..9112072e 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -414,6 +414,7 @@ class Import: self.hhArchiveBase = node.getAttribute("hhArchiveBase") self.saveActions = string_to_bool(node.getAttribute("saveActions"), default=True) self.fastStoreHudCache = string_to_bool(node.getAttribute("fastStoreHudCache"), default=False) + self.saveStarsHH = string_to_bool(node.getAttribute("saveStarsHH"), default=False) def __str__(self): return " interval = %s\n callFpdbHud = %s\n hhArchiveBase = %s\n saveActions = %s\n fastStoreHudCache = %s\n" \ @@ -808,8 +809,12 @@ class Config: try: imp['saveActions'] = self.imp.saveActions except: imp['saveActions'] = True + try: imp['saveStarsHH'] = self.imp.saveStarsHH + except: imp['saveStarsHH'] = False + try: imp['fastStoreHudCache'] = self.imp.fastStoreHudCache except: imp['fastStoreHudCache'] = True + return imp def get_default_paths(self, site = None): From 141b88ecfd9f045d2c108ba684e5032e868e932b Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sat, 20 Feb 2010 17:49:03 +0000 Subject: [PATCH 19/50] stop hudcache updating again when importing duplicate hands --- pyfpdb/Hand.py | 3 +++ pyfpdb/fpdb_import.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 73dd7600..e54354e8 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -125,6 +125,7 @@ class Hand(object): # currency symbol for this hand self.sym = self.SYMBOL[self.gametype['currency']] # save typing! delete this attr when done self.pot.setSym(self.sym) + self.is_duplicate = False # i.e. don't update hudcache if true def __str__(self): vars = ( ("BB", self.bb), @@ -236,6 +237,7 @@ db: a connected Database object""" # TourneysPlayers else: log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo']) + self.is_duplicate = True # i.e. don't update hudcache raise FpdbHandDuplicate(hh['siteHandNo']) def updateHudCache(self, db): @@ -669,6 +671,7 @@ class HoldemOmahaHand(Hand): if self.maxseats is None: self.maxseats = hhc.guessMaxSeats(self) hhc.readOther(self) + #print "\nHand:\n"+str(self) elif builtFrom == "DB": if handid is not None: self.select(handid) # Will need a handId diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index 95ffd525..689858e0 100755 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -456,7 +456,7 @@ class Importer: # FIXME: Need to test for bulk import that isn't rebuilding the cache if self.callHud: for hand in handlist: - if hand is not None: + if hand is not None and not hand.is_duplicate: hand.updateHudCache(self.database) self.database.commit() From 09801cd00e4ea3e8eb6cfd89ecbce105e04d622e Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sat, 20 Feb 2010 18:59:49 +0000 Subject: [PATCH 20/50] first go at db maintenance window, turned off for now --- pyfpdb/Configuration.py | 31 ++++++++------ pyfpdb/Database.py | 92 +++++++++++++++++++++-------------------- pyfpdb/fpdb.py | 38 +++++++++++++---- 3 files changed, 97 insertions(+), 64 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 3d506735..07c96a04 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -695,18 +695,8 @@ class Config: try: db['db-server'] = self.supported_databases[name].db_server except: pass - if self.supported_databases[name].db_server== DATABASE_TYPE_MYSQL: - db['db-backend'] = 2 - elif self.supported_databases[name].db_server== DATABASE_TYPE_POSTGRESQL: - db['db-backend'] = 3 - elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE: - db['db-backend'] = 4 - # sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252) - # feel free to remove or improve this if you understand the problems - # better than me (not hard!) - Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True - else: - raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server) + db['db-backend'] = self.get_backend(self.supported_databases[name].db_server) + return db def set_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None, @@ -725,6 +715,23 @@ class Config: if db_server is not None: self.supported_databases[db_name].dp_server = db_server if db_type is not None: self.supported_databases[db_name].dp_type = db_type return + + def get_backend(self, name): + """Returns the number of the currently used backend""" + if name == DATABASE_TYPE_MYSQL: + ret = 2 + elif name == DATABASE_TYPE_POSTGRESQL: + ret = 3 + elif name == DATABASE_TYPE_SQLITE: + ret = 4 + # sqlcoder: this assignment fixes unicode problems for me with sqlite (windows, cp1252) + # feel free to remove or improve this if you understand the problems + # better than me (not hard!) + Charset.not_needed1, Charset.not_needed2, Charset.not_needed3 = True, True, True + else: + raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server) + + return ret def getDefaultSite(self): "Returns first enabled site or None" diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 0303aad2..debaee59 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -226,7 +226,7 @@ class Database: # create index indexname on tablename (col); - def __init__(self, c, sql = None): + def __init__(self, c, sql = None, autoconnect = True): #log = Configuration.get_logger("logging.conf", "db", log_dir=c.dir_log) log.debug("Creating Database instance, sql = %s" % sql) self.config = c @@ -247,41 +247,42 @@ class Database: else: self.sql = sql - # connect to db - self.do_connect(c) - - if self.backend == self.PGSQL: - from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE - #ISOLATION_LEVEL_AUTOCOMMIT = 0 - #ISOLATION_LEVEL_READ_COMMITTED = 1 - #ISOLATION_LEVEL_SERIALIZABLE = 2 + if autoconnect: + # connect to db + self.do_connect(c) + + if self.backend == self.PGSQL: + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE + #ISOLATION_LEVEL_AUTOCOMMIT = 0 + #ISOLATION_LEVEL_READ_COMMITTED = 1 + #ISOLATION_LEVEL_SERIALIZABLE = 2 - if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion: - log.info("sqlite/:memory: - creating") - self.recreate_tables() - self.wrongDbVersion = False + if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion: + log.info("sqlite/:memory: - creating") + self.recreate_tables() + self.wrongDbVersion = False - self.pcache = None # PlayerId cache - self.cachemiss = 0 # Delete me later - using to count player cache misses - self.cachehit = 0 # Delete me later - using to count player cache hits + self.pcache = None # PlayerId cache + self.cachemiss = 0 # Delete me later - using to count player cache misses + self.cachehit = 0 # Delete me later - using to count player cache hits - # config while trying out new hudcache mechanism - self.use_date_in_hudcache = True + # config while trying out new hudcache mechanism + self.use_date_in_hudcache = True - #self.hud_hero_style = 'T' # Duplicate set of vars just for hero - not used yet. - #self.hud_hero_hands = 2000 # Idea is that you might want all-time stats for others - #self.hud_hero_days = 30 # but last T days or last H hands for yourself + #self.hud_hero_style = 'T' # Duplicate set of vars just for hero - not used yet. + #self.hud_hero_hands = 2000 # Idea is that you might want all-time stats for others + #self.hud_hero_days = 30 # but last T days or last H hands for yourself - # vars for hand ids or dates fetched according to above config: - self.hand_1day_ago = 0 # max hand id more than 24 hrs earlier than now - self.date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) - self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero - self.date_nhands_ago = {} # dates N hands ago per player - not used yet + # vars for hand ids or dates fetched according to above config: + self.hand_1day_ago = 0 # max hand id more than 24 hrs earlier than now + self.date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) + self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero + self.date_nhands_ago = {} # dates N hands ago per player - not used yet - self.saveActions = False if self.import_options['saveActions'] == False else True + self.saveActions = False if self.import_options['saveActions'] == False else True - self.connection.rollback() # make sure any locks taken so far are released + self.connection.rollback() # make sure any locks taken so far are released #end def __init__ # could be used by hud to change hud style @@ -313,7 +314,7 @@ class Database: self.__connected = True def connect(self, backend=None, host=None, database=None, - user=None, password=None): + user=None, password=None, create=False): """Connects a database with the given parameters""" if backend is None: raise FpdbError('Database backend not defined') @@ -384,32 +385,35 @@ class Database: # log.warning("SQLite won't work well without 'sqlalchemy' installed.") if database != ":memory:": - if not os.path.isdir(self.config.dir_database): + if not os.path.isdir(self.config.dir_database) and create: print "Creating directory: '%s'" % (self.config.dir_database) log.info("Creating directory: '%s'" % (self.config.dir_database)) os.mkdir(self.config.dir_database) database = os.path.join(self.config.dir_database, database) self.db_path = database log.info("Connecting to SQLite: %(database)s" % {'database':self.db_path}) - self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES ) - sqlite3.register_converter("bool", lambda x: bool(int(x))) - sqlite3.register_adapter(bool, lambda x: "1" if x else "0") - self.connection.create_function("floor", 1, math.floor) - tmp = sqlitemath() - self.connection.create_function("mod", 2, tmp.mod) - if use_numpy: - self.connection.create_aggregate("variance", 1, VARIANCE) + if os.path.exists(database) or create: + self.connection = sqlite3.connect(self.db_path, detect_types=sqlite3.PARSE_DECLTYPES ) + sqlite3.register_converter("bool", lambda x: bool(int(x))) + sqlite3.register_adapter(bool, lambda x: "1" if x else "0") + self.connection.create_function("floor", 1, math.floor) + tmp = sqlitemath() + self.connection.create_function("mod", 2, tmp.mod) + if use_numpy: + self.connection.create_aggregate("variance", 1, VARIANCE) + else: + log.warning("Some database functions will not work without NumPy support") + self.cursor = self.connection.cursor() + self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes + self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish else: - log.warning("Some database functions will not work without NumPy support") - self.cursor = self.connection.cursor() - self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes - self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish + raise FpdbError("sqlite database "+database+" does not exist") else: - raise FpdbError("unrecognised database backend:"+backend) + raise FpdbError("unrecognised database backend:"+str(backend)) self.cursor = self.connection.cursor() self.cursor.execute(self.sql.query['set tx level']) - self.check_version(database=database, create=True) + self.check_version(database=database, create=create) def check_version(self, database, create): diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 29f5ff6a..7e81d1e6 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -97,6 +97,7 @@ except: import GuiPrefs import GuiLogView +import GuiDatabase import GuiBulkImport import GuiPlayerStats import GuiPositionalStats @@ -288,10 +289,31 @@ class fpdb: dia.destroy() - def dia_create_del_database(self, widget, data=None): - self.warning_box("Unimplemented: Create/Delete Database") - self.obtain_global_lock() - self.release_global_lock() + def dia_maintain_dbs(self, widget, data=None): + self.warning_box("Unimplemented: Maintain Databases") + return + if len(self.tab_names) == 1: + if self.obtain_global_lock(): # returns true if successful + # only main tab has been opened, open dialog + dia = gtk.Dialog("Maintain Databases", + self.window, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) + dia.set_default_size(700, 320) + + prefs = GuiDatabase.GuiDatabase(self.config, self.window, dia) + response = dia.run() + if response == gtk.RESPONSE_ACCEPT: + # save updated config + self.config.save() + + self.release_global_lock() + + dia.destroy() + else: + self.warning_box("Cannot open Database Maintenance window because " + + "other windows have been opened. Re-start fpdb to use this option.") def dia_create_del_user(self, widget, data=None): self.warning_box("Unimplemented: Create/Delete user") @@ -620,7 +642,7 @@ class fpdb: - + @@ -663,7 +685,7 @@ class fpdb: ('sessionreplay', None, '_Session Replayer (todo)', None, 'Session Replayer (todo)', self.not_implemented), ('tableviewer', None, 'Poker_table Viewer (mostly obselete)', None, 'Poker_table Viewer (mostly obselete)', self.tab_table_viewer), ('database', None, '_Database'), - ('createdb', None, 'Create or Delete _Database (todo)', None, 'Create or Delete Database', self.dia_create_del_database), + ('maintaindbs', None, '_Maintain Databases (todo)', None, 'Maintain Databases', self.dia_maintain_dbs), ('createuser', None, 'Create or Delete _User (todo)', None, 'Create or Delete User', self.dia_create_del_user), ('createtabs', None, 'Create or Recreate _Tables', None, 'Create or Recreate Tables ', self.dia_recreate_tables), ('rebuildhudcache', None, 'Rebuild HUD Cache', None, 'Rebuild HUD Cache', self.dia_recreate_hudcache), @@ -685,7 +707,7 @@ class fpdb: window.add_accel_group(accel_group) return menubar - def load_profile(self): + def load_profile(self, create_db = False): """Loads profile from the provided path name.""" self.config = Configuration.Config(file=options.config, dbname=options.dbname) if self.config.file_error: @@ -911,7 +933,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.tab_main_help(None, None) self.window.show() - self.load_profile() + self.load_profile(create_db = True) if not options.errorsToConsole: fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt') From da124770af5ccdb44794422f16290f72350bcc8a Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Mon, 22 Feb 2010 11:45:52 -0500 Subject: [PATCH 21/50] Add souce code locations for 3rd party libs + minor update. --- docs/readme.txt | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/readme.txt b/docs/readme.txt index 03e071d3..e3dbac23 100644 --- a/docs/readme.txt +++ b/docs/readme.txt @@ -1,5 +1,5 @@ README.txt -updated 26 March 2009, REB +updated 22 February 2010, REB fpdb - Free Poker Database @@ -29,7 +29,7 @@ fpdb supports: Omaha (incl Hi/low) 7 Card Stud (incl Hi/low) Razz - Draw support is under development + Triple Draw and Badugi Mixed Games -- HUD under development Operating Systems: @@ -38,23 +38,38 @@ fpdb supports: Mac OS/X -- no support for HUD Databases: + SQLite configured by default MySQL PostgreSQL - SQLite under development Downloads: Releases: http://sourceforge.net/project/showfiles.php?group_id=226872 Development code via git: http://www.assembla.com/spaces/free_poker_tools/trac_git_tool Developers: - At least 7 people have contributed code or patches. Others are welcome. + At least 10 people have contributed code or patches. Others are welcome. +Source Code: + If you received fpdb as the Windows compressed exe, then you did not +receive souce code for fpdb or the included libraries. If you wish, you can +obtain the source code here: + + fpdb: see Downloads, above. + python: http://python.org/ + gtk: http://www.gtk.org/download.html + pygtk: http://www.pygtk.org/downloads.html + psycopg2: http://initd.org/pub/software/psycopg/ + mysqldb: http://sourceforge.net/projects/mysql-python/files/ + sqlalchemy: http://www.sqlalchemy.org/download.html + numpy: http://www.scipy.org/Download + matplotlib: http://sourceforge.net/projects/matplotlib/files/ + License ======= Trademarks of third parties have been used under Fair Use or similar laws. Copyright 2008 Steffen Jobbagy-Felso -Copyright 2009 Ray E. Barker +Copyright 2009,2010 Ray E. Barker Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 as published by the Free Software Foundation; with From 9dac1f1b8409966d7bdc6c968e85ef808c685473 Mon Sep 17 00:00:00 2001 From: Gerko de Roo Date: Tue, 23 Feb 2010 18:55:09 +0100 Subject: [PATCH 22/50] When all players are selected in player stats The site filter settings were bypassed. This fix only selects all players from selected sites --- pyfpdb/GuiPlayerStats.py | 24 ++++++++++++++++++++---- pyfpdb/SQL.py | 3 +++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/pyfpdb/GuiPlayerStats.py b/pyfpdb/GuiPlayerStats.py index 047c1ff1..39c6c727 100644 --- a/pyfpdb/GuiPlayerStats.py +++ b/pyfpdb/GuiPlayerStats.py @@ -81,7 +81,7 @@ class GuiPlayerStats (threading.Thread): self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display) self.filters.registerButton1Name("_Filters") self.filters.registerButton1Callback(self.showDetailFilter) - self.filters.registerButton2Name("_Refresh Stats") + self.filters.registerButton2Name("_Refresh") self.filters.registerButton2Callback(self.refreshStats) # ToDo: store in config @@ -481,7 +481,23 @@ class GuiPlayerStats (threading.Thread): else: gametest = "and gt.category IS NULL" query = query.replace("", gametest) - + + sitetest = "" + q = [] + for m in self.filters.display.items(): + if m[0] == 'Sites' and m[1]: + for n in sitenos: + q.append(n) + if len(q) > 0: + sitetest = str(tuple(q)) + sitetest = sitetest.replace("L", "") + sitetest = sitetest.replace(",)",")") + sitetest = sitetest.replace("u'","'") + sitetest = "and gt.siteId in %s" % sitetest + else: + sitetest = "and gt.siteId IS NULL" + query = query.replace("", sitetest) + if seats: query = query.replace('', 'between ' + str(seats['from']) + ' and ' + str(seats['to'])) if 'show' in seats and seats['show']: @@ -520,7 +536,7 @@ class GuiPlayerStats (threading.Thread): blindtest = str(tuple(nolims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") - bbtest = bbtest + blindtest + ' ) ) )' + bbtest = bbtest + blindtest + ' ) )' else: bbtest = bbtest + '(-1) ) )' if type == 'ring': @@ -539,7 +555,7 @@ class GuiPlayerStats (threading.Thread): query = query.replace("", "") groupLevels = "show" not in str(limits) if groupLevels: - query = query.replace("", "p.name") + query = query.replace("", "-1") else: query = query.replace("", "h.gameTypeId") diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index 6db24fb4..570edb34 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -1915,6 +1915,7 @@ class Sql: inner join Players p on (p.Id = hp.playerId) where hp.playerId in + /*and hp.tourneysPlayersId IS NULL*/ and h.seats @@ -1999,6 +2000,7 @@ class Sql: inner join Players p on (p.Id = hp.playerId) where hp.playerId in + /*and hp.tourneysPlayersId IS NULL*/ and h.seats @@ -2085,6 +2087,7 @@ class Sql: inner join Players p on (p.Id = hp.playerId) where hp.playerId in + /*and hp.tourneysPlayersId IS NULL*/ and h.seats From 03880bbc63f7635821b5ff5ac75796d83ef6a94a Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Wed, 24 Feb 2010 10:28:12 -0500 Subject: [PATCH 23/50] Trivial refactor of get_stats_from_hand for readability. --- pyfpdb/Database.py | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 0303aad2..76addf59 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -643,6 +643,29 @@ class Database: , hero_id = -1 , num_seats = 6 ): + + (hud_style, h_hud_style, query, subs) = self.prep_aggregation(hand, hud_params, hero_id, num_seats) + + c = self.connection.cursor() + +# now get the stats + stat_dict = {} + c.execute(self.sql.query[query], subs) + colnames = [desc[0] for desc in c.description] + for row in c.fetchall(): + playerid = row[0] + if (playerid == hero_id and h_hud_style != 'S') or (playerid != hero_id and hud_style != 'S'): + t_dict = {} + for name, val in zip(colnames, row): + t_dict[name.lower()] = val +# print t_dict + stat_dict[t_dict['player_id']] = t_dict + + return stat_dict + + def prep_aggregation(self, hand, hud_params, hero_id, num_seats): +# This sorts through the all the info having to do with aggregation +# and returns what get_stats needs. hud_style = hud_params['hud_style'] agg_bb_mult = hud_params['agg_bb_mult'] seats_style = hud_params['seats_style'] @@ -652,8 +675,6 @@ class Database: h_seats_style = hud_params['h_seats_style'] h_seats_cust_nums = hud_params['h_seats_cust_nums'] - stat_dict = {} - if seats_style == 'A': seats_min, seats_max = 0, 10 elif seats_style == 'C': @@ -715,23 +736,7 @@ class Database: subs = (hand ,hero_id, stylekey, agg_bb_mult, agg_bb_mult, seats_min, seats_max # hero params ,hero_id, h_stylekey, h_agg_bb_mult, h_agg_bb_mult, h_seats_min, h_seats_max) # villain params - - #print "get stats: hud style =", hud_style, "query =", query, "subs =", subs - c = self.connection.cursor() - -# now get the stats - c.execute(self.sql.query[query], subs) - colnames = [desc[0] for desc in c.description] - for row in c.fetchall(): - playerid = row[0] - if (playerid == hero_id and h_hud_style != 'S') or (playerid != hero_id and hud_style != 'S'): - t_dict = {} - for name, val in zip(colnames, row): - t_dict[name.lower()] = val -# print t_dict - stat_dict[t_dict['player_id']] = t_dict - - return stat_dict + return (hud_style, h_hud_style, query, subs) # uses query on handsplayers instead of hudcache to get stats on just this session def get_stats_from_hand_session(self, hand, stat_dict, hero_id From 6ac76d2d9be0021bbd1a00ecd10d4895f81effe5 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 24 Feb 2010 21:25:19 +0000 Subject: [PATCH 24/50] remove earlier bracket change that screwed stats up - it seemed necessary then but I can't figure out why now --- pyfpdb/GuiPlayerStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/GuiPlayerStats.py b/pyfpdb/GuiPlayerStats.py index 047c1ff1..3c3f46e3 100644 --- a/pyfpdb/GuiPlayerStats.py +++ b/pyfpdb/GuiPlayerStats.py @@ -520,7 +520,7 @@ class GuiPlayerStats (threading.Thread): blindtest = str(tuple(nolims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") - bbtest = bbtest + blindtest + ' ) ) )' + bbtest = bbtest + blindtest + ' ) )' else: bbtest = bbtest + '(-1) ) )' if type == 'ring': From 04e8f117d5ebd47a29307328002d5f1bc49449ef Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Wed, 24 Feb 2010 17:06:36 -0500 Subject: [PATCH 25/50] Revert "Trivial refactor of get_stats_from_hand for readability." This reverts commit 03880bbc63f7635821b5ff5ac75796d83ef6a94a. --- pyfpdb/Database.py | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 76addf59..0303aad2 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -643,29 +643,6 @@ class Database: , hero_id = -1 , num_seats = 6 ): - - (hud_style, h_hud_style, query, subs) = self.prep_aggregation(hand, hud_params, hero_id, num_seats) - - c = self.connection.cursor() - -# now get the stats - stat_dict = {} - c.execute(self.sql.query[query], subs) - colnames = [desc[0] for desc in c.description] - for row in c.fetchall(): - playerid = row[0] - if (playerid == hero_id and h_hud_style != 'S') or (playerid != hero_id and hud_style != 'S'): - t_dict = {} - for name, val in zip(colnames, row): - t_dict[name.lower()] = val -# print t_dict - stat_dict[t_dict['player_id']] = t_dict - - return stat_dict - - def prep_aggregation(self, hand, hud_params, hero_id, num_seats): -# This sorts through the all the info having to do with aggregation -# and returns what get_stats needs. hud_style = hud_params['hud_style'] agg_bb_mult = hud_params['agg_bb_mult'] seats_style = hud_params['seats_style'] @@ -675,6 +652,8 @@ class Database: h_seats_style = hud_params['h_seats_style'] h_seats_cust_nums = hud_params['h_seats_cust_nums'] + stat_dict = {} + if seats_style == 'A': seats_min, seats_max = 0, 10 elif seats_style == 'C': @@ -736,7 +715,23 @@ class Database: subs = (hand ,hero_id, stylekey, agg_bb_mult, agg_bb_mult, seats_min, seats_max # hero params ,hero_id, h_stylekey, h_agg_bb_mult, h_agg_bb_mult, h_seats_min, h_seats_max) # villain params - return (hud_style, h_hud_style, query, subs) + + #print "get stats: hud style =", hud_style, "query =", query, "subs =", subs + c = self.connection.cursor() + +# now get the stats + c.execute(self.sql.query[query], subs) + colnames = [desc[0] for desc in c.description] + for row in c.fetchall(): + playerid = row[0] + if (playerid == hero_id and h_hud_style != 'S') or (playerid != hero_id and hud_style != 'S'): + t_dict = {} + for name, val in zip(colnames, row): + t_dict[name.lower()] = val +# print t_dict + stat_dict[t_dict['player_id']] = t_dict + + return stat_dict # uses query on handsplayers instead of hudcache to get stats on just this session def get_stats_from_hand_session(self, hand, stat_dict, hero_id From a26dfa09f25ac1c5f6369afb246345ea66dd6ddd Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 24 Feb 2010 22:21:13 +0000 Subject: [PATCH 26/50] automation: prompt for gtk dir location and copy files and dirs - resulting dir should be ready for distribution (once zipped up) --- pyfpdb/py2exe_setup.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pyfpdb/py2exe_setup.py b/pyfpdb/py2exe_setup.py index 9975317f..cf9560af 100644 --- a/pyfpdb/py2exe_setup.py +++ b/pyfpdb/py2exe_setup.py @@ -73,6 +73,7 @@ from distutils.core import setup import py2exe import glob import matplotlib +import shutil from datetime import date @@ -111,7 +112,7 @@ def test_and_remove(top): # remove build and dist dirs if they exist test_and_remove('dist') test_and_remove('build') -test_and_remove('gfx') +#test_and_remove('gfx') today = date.today().strftime('%Y%m%d') @@ -174,3 +175,36 @@ dest = dest.replace('\\', '\\\\') os.rename( 'pyfpdb', dest ) +print "Enter directory name for GTK 2.14 (e.g. c:\code\gtk_2.14.7-20090119)\n: ", # the comma means no newline +gtk_dir = sys.stdin.readline().rstrip() + + +print "\ncopying files and dirs from ", gtk_dir, "to", dest.replace('\\\\', '\\'), "..." +src = os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll') +src = src.replace('\\', '\\\\') +shutil.copy( src, dest ) + +src = os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll') +src = src.replace('\\', '\\\\') +shutil.copy( src, dest ) + + +src_dir = os.path.join(gtk_dir, 'etc') +src_dir = src_dir.replace('\\', '\\\\') +dest_dir = os.path.join(dest, 'etc') +dest_dir = dest_dir.replace('\\', '\\\\') +shutil.copytree( src_dir, dest_dir ) + +src_dir = os.path.join(gtk_dir, 'lib') +src_dir = src_dir.replace('\\', '\\\\') +dest_dir = os.path.join(dest, 'lib') +dest_dir = dest_dir.replace('\\', '\\\\') +shutil.copytree( src_dir, dest_dir ) + +src_dir = os.path.join(gtk_dir, 'share') +src_dir = src_dir.replace('\\', '\\\\') +dest_dir = os.path.join(dest, 'share') +dest_dir = dest_dir.replace('\\', '\\\\') +shutil.copytree( src_dir, dest_dir ) + + From 134a13bf298678c4f59bebbd4e443e7f240475d7 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Thu, 25 Feb 2010 20:43:15 +0000 Subject: [PATCH 27/50] create indexes on hudcache in sqlite --- pyfpdb/Database.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index debaee59..d0fe733b 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -142,14 +142,18 @@ class Database: , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} ] , [ # indexes for sqlite (list index 4) - # {'tab':'Players', 'col':'name', 'drop':0} unique indexes not dropped - # {'tab':'Hands', 'col':'siteHandNo', 'drop':0} unique indexes not dropped {'tab':'Hands', 'col':'gametypeId', 'drop':0} , {'tab':'HandsPlayers', 'col':'handId', 'drop':0} , {'tab':'HandsPlayers', 'col':'playerId', 'drop':0} , {'tab':'HandsPlayers', 'col':'tourneyTypeId', 'drop':0} , {'tab':'HandsPlayers', 'col':'tourneysPlayersId', 'drop':0} - #, {'tab':'Tourneys', 'col':'siteTourneyNo', 'drop':0} unique indexes not dropped + , {'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':'Tourneys', 'col':'tourneyTypeId', 'drop':1} + , {'tab':'TourneysPlayers', 'col':'playerId', 'drop':0} + , {'tab':'TourneyTypes', 'col':'siteId', 'drop':0} ] ] @@ -725,6 +729,8 @@ class Database: # now get the stats c.execute(self.sql.query[query], subs) + #for row in c.fetchall(): # needs "explain query plan" in sql statement + # print "query plan: ", row colnames = [desc[0] for desc in c.description] for row in c.fetchall(): playerid = row[0] @@ -2100,6 +2106,7 @@ class HandToWrite: if __name__=="__main__": c = Configuration.Config() + sql = SQL.Sql(db_server = 'sqlite') db_connection = Database(c) # mysql fpdb holdem # db_connection = Database(c, 'fpdb-p', 'test') # mysql fpdb holdem @@ -2117,12 +2124,25 @@ if __name__=="__main__": if hero: print "nutOmatic is id_player = %d" % hero + # example of displaying query plan in sqlite: + if db_connection.backend == 4: + print + c = db_connection.get_cursor() + c.execute('explain query plan '+sql.query['get_table_name'], (h, )) + for row in c.fetchall(): + print "query plan: ", row + print + + t0 = time() stat_dict = db_connection.get_stats_from_hand(h, "ring") + t1 = time() for p in stat_dict.keys(): print p, " ", stat_dict[p] print "cards =", db_connection.get_cards(u'1') db_connection.close_connection + + print "get_stats took: %4.3f seconds" % (t1-t0) print "press enter to continue" sys.stdin.readline() From 70bb9d687dfe441274f717d18aa4be61c8935864 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Thu, 25 Feb 2010 20:57:28 +0000 Subject: [PATCH 28/50] add commented out 'explain query plan' to go with previous commit --- pyfpdb/SQL.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index 6db24fb4..51863698 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -1346,6 +1346,7 @@ class Sql: # same as above except stats are aggregated for all blind/limit levels self.query['get_stats_from_hand_aggregated'] = """ + /* explain query plan */ SELECT hc.playerId AS player_id, max(case when hc.gametypeId = h.gametypeId then hp.seatNo From 5c0e4cb0c2b623effbd8d91d6191d69ca9f8ab8d Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Thu, 25 Feb 2010 20:28:41 -0500 Subject: [PATCH 29/50] Use correct dirs for database and log. --- pyfpdb/Configuration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index d87014e6..d0f81a1b 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -470,7 +470,8 @@ class Config: self.file = file self.dir_self = get_exec_path() - self.dir_config = os.path.dirname(self.file) +# self.dir_config = os.path.dirname(self.file) + self.dir_config = get_default_config_path() self.dir_log = os.path.join(self.dir_config, 'log') self.dir_database = os.path.join(self.dir_config, 'database') self.log_file = os.path.join(self.dir_log, 'fpdb-log.txt') From aca5682daf4fe6a4402ef47592c77a01b3c7ae28 Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Thu, 25 Feb 2010 21:32:49 -0500 Subject: [PATCH 30/50] Get rid of erroneous error message. --- pyfpdb/XTables.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyfpdb/XTables.py b/pyfpdb/XTables.py index a14debc1..28737002 100644 --- a/pyfpdb/XTables.py +++ b/pyfpdb/XTables.py @@ -89,7 +89,6 @@ class Table(Table_Window): # break if window_number is None: - print "Window %s not found. Skipping." % search_string return None # my_geo = self.window.get_geometry() From daeee37b6b04017a7dff151b8b63f7102affc7cd Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sat, 27 Feb 2010 13:56:19 +0000 Subject: [PATCH 31/50] default dbname option is overriding 'default=true' selection in config file --- pyfpdb/Options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py index 6570dbe4..bda28259 100644 --- a/pyfpdb/Options.py +++ b/pyfpdb/Options.py @@ -27,7 +27,7 @@ def fpdb_options(): action="store_true", help="If passed error output will go to the console rather than .") parser.add_option("-d", "--databaseName", - dest="dbname", default="fpdb", + dest="dbname", help="Overrides the default database name") parser.add_option("-c", "--configFile", dest="config", default=None, From 4ed82b1f183d192ed6544ff890cbb9d600305afb Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sat, 27 Feb 2010 15:47:24 +0000 Subject: [PATCH 32/50] comment out DEBUG print --- pyfpdb/Hand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 56a11063..e9626522 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -1449,7 +1449,7 @@ class Pot(object): # Return any uncalled bet. committed = sorted([ (v,k) for (k,v) in self.committed.items()]) - print "DEBUG: committed: %s" % committed + #print "DEBUG: committed: %s" % committed #ERROR below. lastbet is correct in most cases, but wrong when # additional money is committed to the pot in cash games # due to an additional sb being posted. (Speculate that From 6a6d1b1b2c8ffd6e2352db726b9322ba2974cfed Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sat, 27 Feb 2010 18:41:30 +0000 Subject: [PATCH 33/50] fix 3bet stat (was being set to false if someone else 4bet) --- pyfpdb/Database.py | 4 ---- pyfpdb/DerivedStats.py | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index d0fe733b..f51ce5f5 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -917,7 +917,6 @@ class Database: print "warning: constraint %s_%s_fkey not dropped: %s, continuing ..." \ % (fk['fktab'],fk['fkcol'], str(sys.exc_value).rstrip('\n')) else: - print "Only MySQL and Postgres supported so far" return -1 for idx in self.indexes[self.backend]: @@ -952,7 +951,6 @@ class Database: print "warning: index %s_%s_idx not dropped %s, continuing ..." \ % (idx['tab'],idx['col'], str(sys.exc_value).rstrip('\n')) else: - print "Error: Only MySQL and Postgres supported so far" return -1 if self.backend == self.PGSQL: @@ -1007,7 +1005,6 @@ class Database: except: print " create fk failed: " + str(sys.exc_info()) else: - print "Only MySQL and Postgres supported so far" return -1 for idx in self.indexes[self.backend]: @@ -1029,7 +1026,6 @@ class Database: except: print " create index failed: " + str(sys.exc_info()) else: - print "Only MySQL and Postgres supported so far" return -1 if self.backend == self.PGSQL: diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index dddbec8b..b13e7463 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -345,9 +345,9 @@ class DerivedStats(): for action in hand.actions[hand.actionStreets[1]]: # FIXME: fill other(3|4)BStreet0 - i have no idea what does it mean pname, aggr = action[0], action[1] in ('raises', 'bets') - self.handsplayers[pname]['street0_3BChance'] = bet_level == 2 + self.handsplayers[pname]['street0_3BChance'] = self.handsplayers[pname]['street0_3BChance'] or bet_level == 2 self.handsplayers[pname]['street0_4BChance'] = bet_level == 3 - self.handsplayers[pname]['street0_3BDone'] = aggr and (self.handsplayers[pname]['street0_3BChance']) + self.handsplayers[pname]['street0_3BDone'] = self.handsplayers[pname]['street0_3BDone'] or (aggr and self.handsplayers[pname]['street0_3BChance']) self.handsplayers[pname]['street0_4BDone'] = aggr and (self.handsplayers[pname]['street0_4BChance']) if aggr: bet_level += 1 From 8e8444d8f20433fefe8aea0a518bcdc55ba7f036 Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Sat, 27 Feb 2010 16:59:00 -0500 Subject: [PATCH 34/50] Comment out intermediate print. --- pyfpdb/XTables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/XTables.py b/pyfpdb/XTables.py index 28737002..29a9b1d9 100644 --- a/pyfpdb/XTables.py +++ b/pyfpdb/XTables.py @@ -68,7 +68,7 @@ class Table(Table_Window): window_number = None for listing in os.popen('xwininfo -root -tree').readlines(): if re.search(search_string, listing): - print listing +# print listing mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing) self.number = int( mo.group(1), 0) self.width = int( mo.group(4) ) From 14a7784124f2632c52ff18f23572fcfacb37ff16 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Sun, 28 Feb 2010 08:53:44 +0000 Subject: [PATCH 35/50] oops, forgot to add this in earlier commit --- pyfpdb/GuiDatabase.py | 298 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100755 pyfpdb/GuiDatabase.py diff --git a/pyfpdb/GuiDatabase.py b/pyfpdb/GuiDatabase.py new file mode 100755 index 00000000..86c125c9 --- /dev/null +++ b/pyfpdb/GuiDatabase.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#Copyright 2008 Carl Gherardi +#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 in the docs folder of the package. + + +import os +import sys +import traceback +import Queue + +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import pango + +import logging +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("maintdbs") + + +import Exceptions +import Database + + +class GuiDatabase: + + COL_DBMS = 0 + COL_NAME = 1 + COL_DESC = 2 + COL_USER = 3 + COL_PASS = 4 + COL_HOST = 5 + COL_ICON = 6 + + def __init__(self, config, mainwin, dia): + self.config = config + self.main_window = mainwin + self.dia = dia + + try: + #self.dia.set_modal(True) + self.vbox = self.dia.vbox + #gtk.Widget.set_size_request(self.vbox, 700, 400); + + # list of databases in self.config.supported_databases: + self.liststore = gtk.ListStore(str, str, str, str + ,str, str, str, str) #object, gtk.gdk.Pixbuf) + # dbms, name, comment, user, pass, ip, status(, icon?) + # this is how to add a filter: + # + # # Creation of the filter, from the model + # filter = self.liststore.filter_new() + # filter.set_visible_column(1) + # + # # The TreeView gets the filter as model + # self.listview = gtk.TreeView(filter) + self.listview = gtk.TreeView(model=self.liststore) + self.listview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_NONE) + self.listcols = [] + + scrolledwindow = gtk.ScrolledWindow() + scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledwindow.add(self.listview) + self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0) + + refreshbutton = gtk.Button("Refresh") + refreshbutton.connect("clicked", self.refresh, None) + self.vbox.pack_start(refreshbutton, False, False, 3) + refreshbutton.show() + + col = self.addTextColumn("Type", 0, False) + col = self.addTextColumn("Name", 1, False) + col = self.addTextColumn("Description", 2, True) + col = self.addTextColumn("Username", 3, True) + col = self.addTextColumn("Password", 4, True) + col = self.addTextColumn("Host", 5, True) + col = self.addTextObjColumn("", 6) + + self.loadDbs() + + self.dia.connect('response', self.dialog_response_cb) + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print 'guidbmaint: '+ err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) + + def dialog_response_cb(self, dialog, response_id): + # this is called whether close button is pressed or window is closed + dialog.destroy() + + + def get_dialog(self): + return self.dia + + def addTextColumn(self, title, n, editable=False): + col = gtk.TreeViewColumn(title) + self.listview.append_column(col) + + cRender = gtk.CellRendererText() + cRender.set_property("wrap-mode", pango.WRAP_WORD_CHAR) + cRender.set_property('editable', editable) + cRender.connect('edited', self.edited_cb, (self.liststore,n)) + + col.pack_start(cRender, True) + col.add_attribute(cRender, 'text', n) + col.set_max_width(1000) + col.set_spacing(0) # no effect + self.listcols.append(col) + col.set_clickable(True) + col.connect("clicked", self.sortCols, n) + + return(col) + + def edited_cb(self, cell, path, new_text, user_data): + liststore, col = user_data + valid = True + name = self.liststore[path][self.COL_NAME] + + # Validate new value (only for dbms so far, but dbms now not updateable so no validation at all!) + #if col == self.COL_DBMS: + # if new_text not in Configuration.DATABASE_TYPES: + # valid = False + + if valid: + self.liststore[path][col] = new_text + + self.config.set_db_parameters( db_server = self.liststore[path][self.COL_DBMS] + , db_name = name + , db_ip = self.liststore[path][self.COL_HOST] + , db_user = self.liststore[path][self.COL_USER] + , db_pass = self.liststore[path][self.COL_PASS] ) + + return + + def check_new_name(self, path, new_text): + name_ok = True + for i,db in enumerate(self.liststore): + if i != path and new_text == db[self.COL_NAME]: + name_ok = False + #TODO: popup an error message telling user names must be unique + return name_ok + + def addTextObjColumn(self, title, n): + col = gtk.TreeViewColumn(title) + self.listview.append_column(col) + + cRenderT = gtk.CellRendererText() + cRenderT.set_property("wrap-mode", pango.WRAP_WORD_CHAR) + col.pack_start(cRenderT, False) + col.add_attribute(cRenderT, 'text', n) + + cRenderP = gtk.CellRendererPixbuf() + col.pack_start(cRenderP, False) + col.add_attribute(cRenderP, 'stock-id', n+1) + + col.set_max_width(1000) + col.set_spacing(0) # no effect + self.listcols.append(col) + #col.set_clickable(True) + #col.connect("clicked", self.sortCols, p) + return(col) + + def loadDbs(self): + + self.liststore.clear() + self.listcols = [] + self.dbs = [] # list of tuples: (dbms, name, comment, user, passwd, host, status, icon) + + try: + # want to fill: dbms, name, comment, user, passwd, host, status(, icon?) + for name in self.config.supported_databases: #db_ip/db_user/db_pass/db_server + dbms = self.config.supported_databases[name].db_server # mysql/postgresql/sqlite + dbms_num = self.config.get_backend(dbms) # 2 / 3 / 4 + comment = "" + if dbms == 'sqlite': + user = "" + passwd = "" + else: + user = self.config.supported_databases[name].db_user + passwd = self.config.supported_databases[name].db_pass + host = self.config.supported_databases[name].db_ip + status = "" + icon = None + err_msg = "" + + db = Database.Database(self.config, sql = None, autoconnect = False) + # try to connect to db, set status and err_msg if it fails + try: + # is creating empty db for sqlite ... mod db.py further? + # add noDbTables flag to db.py? + db.connect(backend=dbms_num, host=host, database=name, user=user, password=passwd, create=False) + if db.connected: + status = 'ok' + icon = gtk.STOCK_APPLY + if db.wrongDbVersion: + status = 'old' + icon = gtk.STOCK_INFO + except Exceptions.FpdbMySQLAccessDenied: + err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?" + status = "failed" + icon = gtk.STOCK_CANCEL + except Exceptions.FpdbMySQLNoDatabase: + err_msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - " \ + + "Please check that the MySQL service has been started" + status = "failed" + icon = gtk.STOCK_CANCEL + except Exceptions.FpdbPostgresqlAccessDenied: + err_msg = "Postgres Server reports: Access denied. Are your permissions set correctly?" + status = "failed" + except Exceptions.FpdbPostgresqlNoDatabase: + err_msg = "Postgres client reports: Unable to connect - " \ + + "Please check that the Postgres service has been started" + status = "failed" + icon = gtk.STOCK_CANCEL + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + log.info( 'db connection to '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' + + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) ) + status = "failed" + icon = gtk.STOCK_CANCEL + + b = gtk.Button(name) + b.show() + iter = self.liststore.append( (dbms, name, comment, user, passwd, host, status, icon) ) + + self.listview.show() + scrolledwindow.show() + self.vbox.show() + self.dia.set_focus(self.listview) + + self.vbox.show_all() + self.dia.show() + except: + err = traceback.extract_tb(sys.exc_info()[2])[-1] + print 'loaddbs error: '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' \ + + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) + + def sortCols(self, col, n): + try: + if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING: + col.set_sort_order(gtk.SORT_DESCENDING) + else: + col.set_sort_order(gtk.SORT_ASCENDING) + self.liststore.set_sort_column_id(n, col.get_sort_order()) + #self.liststore.set_sort_func(n, self.sortnums, (n,grid)) + for i in xrange(len(self.listcols)): + self.listcols[i].set_sort_indicator(False) + self.listcols[n].set_sort_indicator(True) + # use this listcols[col].set_sort_indicator(True) + # to turn indicator off for other cols + except: + err = traceback.extract_tb(sys.exc_info()[2]) + print "***sortCols error: " + str(sys.exc_info()[1]) + print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] ) + + def refresh(self, widget, data): + self.loadDbs() + + + +if __name__=="__main__": + + config = Configuration.Config() + + win = gtk.Window(gtk.WINDOW_TOPLEVEL) + win.set_title("Test Log Viewer") + win.set_border_width(1) + win.set_default_size(600, 500) + win.set_resizable(True) + + dia = gtk.Dialog("Log Viewer", + win, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) + dia.set_default_size(500, 500) + log = GuiLogView(config, win, dia.vbox) + response = dia.run() + if response == gtk.RESPONSE_ACCEPT: + pass + dia.destroy() + + + + From 6daf2382415a666d470aa75a0ab938c7f1a1158d Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Sun, 28 Feb 2010 22:20:09 -0500 Subject: [PATCH 36/50] Add readme.txt to the exe. --- pyfpdb/py2exe_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/py2exe_setup.py b/pyfpdb/py2exe_setup.py index cf9560af..0db53a40 100644 --- a/pyfpdb/py2exe_setup.py +++ b/pyfpdb/py2exe_setup.py @@ -152,7 +152,7 @@ setup( }, # files in 2nd value in tuple are moved to dir named in 1st value - data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf']) + data_files = [('', ['HUD_config.xml.example', 'Cards01.png', 'logging.conf', '../docs/readme.txt']) ,(dist_dir, [r'..\run_fpdb.bat']) ,( dist_dir + r'\gfx', glob.glob(r'..\gfx\*.*') ) # line below has problem with fonts subdir ('not a regular file') From 444ec73f1dc02c9579c2983f16fb8157127a437a Mon Sep 17 00:00:00 2001 From: Eratosthenes Date: Mon, 1 Mar 2010 19:26:45 -0500 Subject: [PATCH 37/50] Update for changes to Tables.Table() call. --- pyfpdb/Tables_Demo.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyfpdb/Tables_Demo.py b/pyfpdb/Tables_Demo.py index 397579e7..92bb1a3c 100755 --- a/pyfpdb/Tables_Demo.py +++ b/pyfpdb/Tables_Demo.py @@ -32,12 +32,16 @@ import gtk import gobject # fpdb/free poker tools modules +import Configuration +from HandHistoryConverter import getTableTitleRe + # get the correct module for the current os if os.name == 'posix': import XTables as Tables elif os.name == 'nt': import WinTables as Tables +config = Configuration.Config() # Main function used for testing if __name__=="__main__": # c = Configuration.Config() @@ -82,11 +86,16 @@ if __name__=="__main__": (tour_no, tab_no) = table_name.split(",", 1) tour_no = tour_no.rstrip() tab_no = tab_no.rstrip() - table = Tables.Table(None, tournament = tour_no, table_number = tab_no) + type = "tour" + table_kwargs = dict(tournament = tour_no, table_number = tab_no) else: # not a tournament print "cash game" table_name = table_name.rstrip() - table = Tables.Table(None, table_name = table_name) + type = "cash" + table_kwargs = dict(table_name = table_name) + + search_string = getTableTitleRe(config, "Full Tilt Poker", type, **table_kwargs) + table = Tables.Table(search_string, **table_kwargs) table.gdk_handle = gtk.gdk.window_foreign_new(table.number) print "table =", table From 6a176090407b5fc5542b795441a224b4c52fa5ee Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 2 Mar 2010 15:51:40 +0800 Subject: [PATCH 38/50] FullTilt: Playernames can be 2 chars long Found a hand history where the playername was 'OG'. The parser assumed player names were a minimum of 3 characters. --- pyfpdb/FulltiltToFpdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index 2a3050ab..10ecb8dd 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -65,8 +65,8 @@ class Fulltilt(HandHistoryConverter): (\s\((?PTurbo)\))?)|(?P.+)) ''', re.VERBOSE) re_Button = re.compile('^The button is in seat #(?P