From 4f1ad75a9a176d5e8dc1a477bfd6d0d992ff9911 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 2 Mar 2011 17:12:04 +0800 Subject: [PATCH 01/23] Regression: 3 FTP flop files - Observed.No.player.stacks: Throws an error - Observed.Hand.with.chat: Old Omaha hand with different Gametype line - Run.it.Twice.Archive.Shallow: Hand with full RIT, Shallow table --- ...25-50.200610.Observed.No.player.stacks.txt | 17 +++++ ...200-400.200612.Observed.Hand.with.chat.txt | 65 +++++++++++++++++++ ...50.201012.Run.it.Twice.Archive.Shallow.txt | 60 +++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/NLHE-6max-USD-25-50.200610.Observed.No.player.stacks.txt create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-200-400.200612.Observed.Hand.with.chat.txt create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-25-50.201012.Run.it.Twice.Archive.Shallow.txt diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/NLHE-6max-USD-25-50.200610.Observed.No.player.stacks.txt b/pyfpdb/regression-test-files/cash/FTP/Flop/NLHE-6max-USD-25-50.200610.Observed.No.player.stacks.txt new file mode 100644 index 00000000..f7b9fbc9 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/NLHE-6max-USD-25-50.200610.Observed.No.player.stacks.txt @@ -0,0 +1,17 @@ +Full Tilt Poker Game #1083884910: Table Coral Isle (6 max) - $25/$50 - No Limit Hold'em - 17:47:12 ET - 2006/10/09 +cinidy lynn posts the small blind of $25 +durrrr posts the big blind of $50 +The button is in seat #3 +*** HOLE CARDS *** +cinidy lynn raises to $1,000, and is all in +durrrr folds +Uncalled bet of $950 returned to cinidy lynn +cinidy lynn mucks +cinidy lynn wins the pot ($100) +*** SUMMARY *** +Total pot $100 | Rake $0 +Seat 3: cinidy lynn (small blind) collected ($100), mucked +Seat 4: durrrr (big blind) folded before the Flop + + + diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-200-400.200612.Observed.Hand.with.chat.txt b/pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-200-400.200612.Observed.Hand.with.chat.txt new file mode 100644 index 00000000..e33d5c76 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-200-400.200612.Observed.Hand.with.chat.txt @@ -0,0 +1,65 @@ +Full Tilt Poker Game #1463159669: Table Pantheon (6 max) - $200/$400 - Pot Limit Omaha - 20:47:44 ET - 2006/12/23 +Seat 1: tjalfe ($24,578) +Seat 2: CrazyZachary ($8,001.50) +Seat 3: sbrugby ($38,000) +Seat 4: Phil Ivey ($69,970) +Seat 5: Son-in-Law ($37,381.50) +Seat 6: Gus Hansen ($60,478.50) +Son-in-Law posts the small blind of $200 +Gus Hansen posts the big blind of $400 +The button is in seat #4 +*** HOLE CARDS *** +Hence (Observer): lolz +tjalfe raises to $1,400 +Round3rr (Observer): it's tru man +CrazyZachary has 15 seconds left to act +Round3rr (Observer): if he just sent me 1 big blind look @ his stack +Stacks987 (Observer): stop begging +CrazyZachary adds $31,998.50 +CrazyZachary folds +sbrugby folds +Phil Ivey calls $1,400 +Stacks987 (Observer): get a job +JeremyAlan21 (Observer): y would he give u money +JeremyAlan21 (Observer): ##% +Son-in-Law calls $1,200 +Stacks987 (Observer): yeah that makes no sense +Round3rr (Observer): y not +Round3rr (Observer): he has 60k in front of him +Stacks987 (Observer): why would he? +Round3rr (Observer): 80k @ the other table +Gus Hansen folds +Hence (Observer): i was talking about the hand +*** FLOP *** [Ks Ah Qh] +Son-in-Law checks +Urfo618 (Observer): cuz ur a donkey stacks +tjalfe checks +Stacks987 (Observer): stfu urf +jordanlj (Observer): rounder ur a loser +Stacks987 (Observer): lol +Phil Ivey has 15 seconds left to act +Stacks987 (Observer): im having fun leave me alone +Phil Ivey checks +*** TURN *** [Ks Ah Qh] [Jd] +jordanlj (Observer): u might get a big blind for anal or maybe not +Son-in-Law bets $3,600 +Round3rr (Observer): anal ? +R U NUTZ 2 (Observer): lol +tjalfe folds +Phil Ivey folds +Uncalled bet of $3,600 returned to Son-in-Law +Son-in-Law mucks +Son-in-Law wins the pot ($4,597) +jordanlj (Observer): yeh poop sex +*** SUMMARY *** +Total pot $4,600 | Rake $3 +Board: [Ks Ah Qh Jd] +Seat 1: tjalfe folded on the Turn +Seat 2: CrazyZachary didn't bet (folded) +Seat 3: sbrugby didn't bet (folded) +Seat 4: Phil Ivey (button) folded on the Turn +Seat 5: Son-in-Law (small blind) collected ($4,597), mucked +Seat 6: Gus Hansen (big blind) folded before the Flop + + + diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-25-50.201012.Run.it.Twice.Archive.Shallow.txt b/pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-25-50.201012.Run.it.Twice.Archive.Shallow.txt new file mode 100644 index 00000000..5626d9c5 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/PLO-6max-USD-25-50.201012.Run.it.Twice.Archive.Shallow.txt @@ -0,0 +1,60 @@ +FullTiltPoker Game #26063988000: Table Teddy (6 max, shallow) - $25/$50 - Pot Limit Omaha Hi - 18:15:00 ET - 2010/12/01 + +Seat 1: Player1 ($1,000) +Seat 3: Player3 ($3,423) +Seat 4: Player4 ($1,611) +Seat 6: Hero ($1,835.50) +Hero posts the small blind of $25 +Player1 posts the big blind of $50 +The button is in seat #4 +*** HOLE CARDS *** +Dealt to Hero [9s Jc 8c 7s] +Player3 raises to $112.50 +Player4 folds +Hero has 15 seconds left to act +Hero has requested TIME +Hero raises to $387.50 +Player1 folds +Player3 calls $275 +*** FLOP *** [6c Qh 3c] +Hero has 15 seconds left to act +Hero has requested TIME +Hero bets $825 +Player3 has 15 seconds left to act +Player3 raises to $3,035.50, and is all in +Hero calls $623, and is all in +Players agree to Run It Twice +Player3 shows [2s 9c Ac 5s] +Hero shows [9s Jc 8c 7s] +Uncalled bet of $1,587.50 returned to Player3 +*** TURN 1 *** [6c Qh 3c] [3d] +*** RIVER 1 *** [6c Qh 3c 3d] [Th] +*** TURN 2 *** [6c Qh 3c] [9h] +*** RIVER 2 *** [6c Qh 3c 9h] [6h] +*** SHOW DOWN 1 *** +Player3 shows a pair of Threes +Hero shows a pair of Threes +*** SHOW DOWN 2 *** +Player3 shows two pair, Nines and Sixes +Hero shows two pair, Nines and Sixes +Player3 wins pot 1 ($1,859) with a pair of Threes +Player3 wins pot 2 ($1,859) with two pair, Nines and Sixes +*** SUMMARY *** +Duration 65s +Total pot $3,721 | Rake $3 +*** SUMMARY 1 *** +Pot 1 $1,859 +Board: [6c Qh 3c 3d Th] +Seat 1: Player1 (big blind) folded before the Flop +Seat 3: Player3 showed [2s 9c Ac 5s] and won ($1,859) with a pair of Threes +Seat 4: Player4 (button) didn't bet (folded) +Seat 6: Hero (small blind) showed [9s Jc 8c 7s] and lost with a pair of Threes +*** SUMMARY 2 *** +Pot 2 $1,859 +Board: [6c Qh 3c 9h 6h] +Seat 1: Player1 (big blind) folded before the Flop +Seat 3: Player3 showed [2s 9c Ac 5s] and won ($1,859) with two pair, Nines and Sixes +Seat 4: Player4 (button) didn't bet (folded) +Seat 6: Hero (small blind) showed [9s Jc 8c 7s] and lost with two pair, Nines and Sixes + + From e6df3cd8098fce5c0e2e0d7259eb9a96c74caa29 Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 5 Mar 2011 17:47:01 +0800 Subject: [PATCH 02/23] FTP: Mod game regexes for older Hi/Lo format --- pyfpdb/FulltiltToFpdb.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py index b63cec83..a08475ed 100755 --- a/pyfpdb/FulltiltToFpdb.py +++ b/pyfpdb/FulltiltToFpdb.py @@ -74,7 +74,7 @@ class Fulltilt(HandHistoryConverter): (Ante\s\$?(?P[%(NUM)s]+)\s)?-\s [%(LS)s]?(?P[%(NUM)s]+\sCap\s)? (?P(No\sLimit|Pot\sLimit|Limit))?\s - (?P(Hold\'em|Omaha\sHi|Omaha\sH/L|Omaha|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi|2-7\sTriple\sDraw|5\sCard\sDraw|Badugi)) + (?P(Hold\'em|Omaha(\sH/L|\sHi/Lo|\sHi|)|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi|2-7\sTriple\sDraw|5\sCard\sDraw|Badugi)) ''' % substitutions, re.VERBOSE) re_SplitHands = re.compile(r"\n\n\n+") re_TailSplitHands = re.compile(r"(\n\n+)") @@ -114,7 +114,7 @@ class Fulltilt(HandHistoryConverter): (\((?PTurbo)\)\s)? \((?P\d+)\)\s ((?PMatch\s\d)\s)? - (?P(Hold\'em|Omaha\sHi|Omaha\sH/L|Omaha|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))\s + (?P(Hold\'em|Omaha(\sHi/Lo|\sH/L|\sHi|)|7\sCard\sStud|Stud\sH/L|Razz|Stud\sHi))\s (\((?PTurbo)\)\s)? (?P(No\sLimit|Pot\sLimit|Limit))? ''' % substitutions, re.VERBOSE) @@ -198,11 +198,6 @@ class Fulltilt(HandHistoryConverter): ] def determineGameType(self, handText): - # Full Tilt Poker Game #10777181585: Table Deerfly (deep 6) - $0.01/$0.02 - Pot Limit Omaha Hi - 2:24:44 ET - 2009/02/22 - # Full Tilt Poker Game #10773265574: Table Butte (6 max) - $0.01/$0.02 - Pot Limit Hold'em - 21:33:46 ET - 2009/02/21 - # Full Tilt Poker Game #9403951181: Table CR - tay - $0.05/$0.10 - No Limit Hold'em - 9:40:20 ET - 2008/12/09 - # Full Tilt Poker Game #10809877615: Table Danville - $0.50/$1 Ante $0.10 - Limit Razz - 21:47:27 ET - 2009/02/23 - # Full Tilt Poker.fr Game #23057874034: Table Douai–Lens (6 max) - €0.01/€0.02 - No Limit Hold'em - 21:59:17 CET - 2010/08/13 info = {'type':'ring'} m = self.re_GameInfo.search(handText) @@ -220,6 +215,7 @@ class Fulltilt(HandHistoryConverter): 'Omaha Hi' : ('hold','omahahi'), 'Omaha' : ('hold','omahahi'), 'Omaha H/L' : ('hold','omahahilo'), + 'Omaha Hi/Lo' : ('hold','omahahilo'), 'Razz' : ('stud','razz'), 'Stud Hi' : ('stud','studhi'), 'Stud H/L' : ('stud','studhilo'), From 43e4888a8d77e673f77ab3fa7dcf7fbfd6efad9c Mon Sep 17 00:00:00 2001 From: Worros Date: Sat, 5 Mar 2011 17:47:53 +0800 Subject: [PATCH 03/23] Regression: FTP LO8 8 max observed hand --- ...200-400.200610.Observed.Hand.with.chat.txt | 62 +++ ...-400.200610.Observed.Hand.with.chat.txt.gt | 1 + ...0.200610.Observed.Hand.with.chat.txt.hands | 31 ++ ...-400.200610.Observed.Hand.with.chat.txt.hp | 525 ++++++++++++++++++ 4 files changed, 619 insertions(+) create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.gt create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hands create mode 100644 pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hp diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt new file mode 100644 index 00000000..eb6e7c44 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt @@ -0,0 +1,62 @@ +Full Tilt Poker Game #1088791321: Table Vero (6 max) - $200/$400 - Limit Omaha Hi/Lo - 0:03:34 ET - 2006/10/11 +Seat 1: zbubop ($10,676) +Seat 2: AllenCunningham ($6,025) +Seat 3: TexasLimitDonk ($6,816) +Seat 4: Mike Matusow ($10,689), is sitting out +Seat 5: Erick Lindgren ($4,339.50) +Seat 6: Shoe Lab ($15,968.50) +Erick Lindgren posts the small blind of $100 +Shoe Lab posts the big blind of $200 + +The button is in seat #3 +*** HOLE CARDS *** +tyler13jack (Observer): mark karr HAHAHAHAAHA +zbubop calls $200 +Skylerjade (Observer): HAHAHAHA YOUR SIK CROW +AllenCunningham raises to $400 +TexasLimitDonk folds +Right is Tight (Observer): phil is just nasty +Skylerjade (Observer): he does though +Erick Lindgren folds +GE1K0 (Observer): u ok there cunningham? +Crow1974 (Observer): i said looks +Shoe Lab calls $200 +zbubop calls $200 +*** FLOP *** [7h 8s 3s] +Crow1974 (Observer): in that pic +Shoe Lab checks +eagle604 (Observer): did they win? +zbubop checks +Crow1974 (Observer): not in real life +AllenCunningham bets $200 +Skylerjade (Observer): My girlfirend thinks hes hott in real life +Shoe Lab folds +zbubop calls $200 +*** TURN *** [7h 8s 3s] [4s] +crobbins92020 (Observer): he isn't that weird...got a ride home first class..w/ drinks +zbubop checks +crobbins92020 (Observer): lol +AllenCunningham bets $400 +Pavel Datsyuk (Observer): hes got a dif expression at a diff table +zbubop raises to $800 +AllenCunningham calls $400 +*** RIVER *** [7h 8s 3s 4s] [Ah] +zbubop bets $400 +AllenCunningham calls $400 +*** SHOW DOWN *** +zbubop shows [3c 9h As Qs] (a flush, Ace high) +*** SHOW DOWN *** + (8,7,4,3,A) +AllenCunningham shows [7d 2c Ks Ad] (two pair, Aces and Sevens) + (7,4,3,2,A) +zbubop wins the high pot ($2,048.50) with a flush, Ace high +AllenCunningham wins the low pot ($2,048.50) with 7,4,3,2,A +*** SUMMARY *** +Total pot $4,100 | Rake $3 +Board: [7h 8s 3s 4s Ah] +Seat 1: zbubop showed [3c 9h As Qs] and won ($2,048.50) with HI: a flush, Ace high; LO: 8,7,4,3,A +Seat 2: AllenCunningham showed [7d 2c Ks Ad] and won ($2,048.50) with HI: two pair, Aces and Sevens; LO: 7,4,3,2,A +Seat 3: TexasLimitDonk (button) didn't bet (folded) +Seat 4: Mike Matusow is sitting out +Seat 5: Erick Lindgren (small blind) folded before the Flop +Seat 6: Shoe Lab (big blind) folded on the Flop diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.gt b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.gt new file mode 100644 index 00000000..b9459768 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.gt @@ -0,0 +1 @@ +(1, 'USD', 'ring', 'hold', 'omahahilo', 'fl', 's', 10000, 20000, 20000, 40000) diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hands b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hands new file mode 100644 index 00000000..64bcafb2 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hands @@ -0,0 +1,31 @@ +{ 'boardcard1': 6, + 'boardcard2': 46, + 'boardcard3': 41, + 'boardcard4': 42, + 'boardcard5': 13, + 'gametypeId': 21, + 'importTime': None, + 'maxSeats': 6, + 'playersAtShowdown': 2, + 'playersAtStreet1': 3, + 'playersAtStreet2': 2, + 'playersAtStreet3': 2, + 'playersAtStreet4': 0, + 'playersVpi': 3, + 'seats': 5, + 'sessionId': None, + 'showdownPot': 0, + 'siteHandNo': u'1088791321', + 'startTime': datetime.datetime(2006, 10, 11, 4, 3, 34, tzinfo=pytz.utc), + 'street0Raises': 1, + 'street1Pot': 170000, + 'street1Raises': 1, + 'street2Pot': 330000, + 'street2Raises': 2, + 'street3Pot': 410000, + 'street3Raises': 1, + 'street4Pot': 0, + 'street4Raises': 0, + 'tableName': u'Vero', + 'texture': None, + 'tourneyId': None} diff --git a/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hp b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hp new file mode 100644 index 00000000..f2a1abb6 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/FTP/Flop/LO8-6max-USD-200-400.200610.Observed.Hand.with.chat.txt.hp @@ -0,0 +1,525 @@ +{ u'AllenCunningham': { 'card1': 19, + 'card2': 27, + 'card3': 51, + 'card4': 26, + 'card5': 0, + 'card6': 0, + 'card7': 0, + 'foldBbToStealChance': False, + 'foldSbToStealChance': False, + 'foldToOtherRaisedStreet0': False, + 'foldToOtherRaisedStreet1': False, + 'foldToOtherRaisedStreet2': False, + 'foldToOtherRaisedStreet3': False, + 'foldToOtherRaisedStreet4': False, + 'foldToStreet1CBChance': False, + 'foldToStreet1CBDone': False, + 'foldToStreet2CBChance': False, + 'foldToStreet2CBDone': False, + 'foldToStreet3CBChance': False, + 'foldToStreet3CBDone': False, + 'foldToStreet4CBChance': False, + 'foldToStreet4CBDone': False, + 'foldedBbToSteal': False, + 'foldedSbToSteal': False, + 'other3BStreet0': False, + 'other4BStreet0': False, + 'otherRaisedStreet0': False, + 'otherRaisedStreet1': False, + 'otherRaisedStreet2': True, + 'otherRaisedStreet3': True, + 'otherRaisedStreet4': False, + 'position': 1, + 'raiseFirstInChance': False, + 'raiseToStealChance': False, + 'raiseToStealDone': False, + 'raisedFirstIn': False, + 'rake': 150, + 'sawShowdown': True, + 'seatNo': 2, + 'sitout': False, + 'startCards': 0, + 'startCash': 602500, + 'street0Aggr': True, + 'street0Bets': 0, + 'street0Calls': 0, + 'street0Raises': 0, + 'street0VPI': True, + 'street0_3BChance': False, + 'street0_3BDone': False, + 'street0_4BChance': False, + 'street0_4BDone': False, + 'street0_C4BChance': False, + 'street0_C4BDone': False, + 'street0_FoldTo3BChance': False, + 'street0_FoldTo3BDone': False, + 'street0_FoldTo4BChance': False, + 'street0_FoldTo4BDone': False, + 'street0_SqueezeChance': False, + 'street0_SqueezeDone': False, + 'street1Aggr': True, + 'street1Bets': 1, + 'street1CBChance': True, + 'street1CBDone': True, + 'street1Calls': 0, + 'street1CheckCallRaiseChance': False, + 'street1CheckCallRaiseDone': False, + 'street1Raises': 0, + 'street1Seen': True, + 'street2Aggr': True, + 'street2Bets': 1, + 'street2CBChance': True, + 'street2CBDone': True, + 'street2Calls': 1, + 'street2CheckCallRaiseChance': False, + 'street2CheckCallRaiseDone': False, + 'street2Raises': 0, + 'street2Seen': True, + 'street3Aggr': False, + 'street3Bets': 0, + 'street3CBChance': False, + 'street3CBDone': False, + 'street3Calls': 1, + 'street3CheckCallRaiseChance': False, + 'street3CheckCallRaiseDone': False, + 'street3Raises': 0, + 'street3Seen': True, + 'street4Aggr': False, + 'street4Bets': 0, + 'street4CBChance': False, + 'street4CBDone': False, + 'street4Calls': 0, + 'street4CheckCallRaiseChance': False, + 'street4CheckCallRaiseDone': False, + 'street4Raises': 0, + 'street4Seen': False, + 'success_Steal': False, + 'totalProfit': 24850, + 'tourneyTypeId': None, + 'tourneysPlayersIds': None, + 'winnings': 204850, + 'wonAtSD': 1.0, + 'wonWhenSeenStreet1': 1.0, + 'wonWhenSeenStreet2': 1.0, + 'wonWhenSeenStreet3': 1.0, + 'wonWhenSeenStreet4': 0.0}, + u'Erick Lindgren': { 'card1': 0, + 'card2': 0, + 'card3': 0, + 'card4': 0, + 'card5': 0, + 'card6': 0, + 'card7': 0, + 'foldBbToStealChance': False, + 'foldSbToStealChance': False, + 'foldToOtherRaisedStreet0': False, + 'foldToOtherRaisedStreet1': False, + 'foldToOtherRaisedStreet2': False, + 'foldToOtherRaisedStreet3': False, + 'foldToOtherRaisedStreet4': False, + 'foldToStreet1CBChance': False, + 'foldToStreet1CBDone': False, + 'foldToStreet2CBChance': False, + 'foldToStreet2CBDone': False, + 'foldToStreet3CBChance': False, + 'foldToStreet3CBDone': False, + 'foldToStreet4CBChance': False, + 'foldToStreet4CBDone': False, + 'foldedBbToSteal': False, + 'foldedSbToSteal': False, + 'other3BStreet0': False, + 'other4BStreet0': False, + 'otherRaisedStreet0': False, + 'otherRaisedStreet1': False, + 'otherRaisedStreet2': False, + 'otherRaisedStreet3': False, + 'otherRaisedStreet4': False, + 'position': 'S', + 'raiseFirstInChance': False, + 'raiseToStealChance': False, + 'raiseToStealDone': False, + 'raisedFirstIn': False, + 'rake': 0, + 'sawShowdown': False, + 'seatNo': 5, + 'sitout': False, + 'startCards': 0, + 'startCash': 433950, + 'street0Aggr': False, + 'street0Bets': 0, + 'street0Calls': 0, + 'street0Raises': 0, + 'street0VPI': False, + 'street0_3BChance': True, + 'street0_3BDone': False, + 'street0_4BChance': False, + 'street0_4BDone': False, + 'street0_C4BChance': False, + 'street0_C4BDone': False, + 'street0_FoldTo3BChance': False, + 'street0_FoldTo3BDone': False, + 'street0_FoldTo4BChance': False, + 'street0_FoldTo4BDone': False, + 'street0_SqueezeChance': False, + 'street0_SqueezeDone': False, + 'street1Aggr': False, + 'street1Bets': 0, + 'street1CBChance': False, + 'street1CBDone': False, + 'street1Calls': 0, + 'street1CheckCallRaiseChance': False, + 'street1CheckCallRaiseDone': False, + 'street1Raises': 0, + 'street1Seen': False, + 'street2Aggr': False, + 'street2Bets': 0, + 'street2CBChance': False, + 'street2CBDone': False, + 'street2Calls': 0, + 'street2CheckCallRaiseChance': False, + 'street2CheckCallRaiseDone': False, + 'street2Raises': 0, + 'street2Seen': False, + 'street3Aggr': False, + 'street3Bets': 0, + 'street3CBChance': False, + 'street3CBDone': False, + 'street3Calls': 0, + 'street3CheckCallRaiseChance': False, + 'street3CheckCallRaiseDone': False, + 'street3Raises': 0, + 'street3Seen': False, + 'street4Aggr': False, + 'street4Bets': 0, + 'street4CBChance': False, + 'street4CBDone': False, + 'street4Calls': 0, + 'street4CheckCallRaiseChance': False, + 'street4CheckCallRaiseDone': False, + 'street4Raises': 0, + 'street4Seen': False, + 'success_Steal': False, + 'totalProfit': -10000, + 'tourneyTypeId': None, + 'tourneysPlayersIds': None, + 'winnings': 0, + 'wonAtSD': 0.0, + 'wonWhenSeenStreet1': 0.0, + 'wonWhenSeenStreet2': 0.0, + 'wonWhenSeenStreet3': 0.0, + 'wonWhenSeenStreet4': 0.0}, + u'Shoe Lab': { 'card1': 0, + 'card2': 0, + 'card3': 0, + 'card4': 0, + 'card5': 0, + 'card6': 0, + 'card7': 0, + 'foldBbToStealChance': False, + 'foldSbToStealChance': False, + 'foldToOtherRaisedStreet0': False, + 'foldToOtherRaisedStreet1': True, + 'foldToOtherRaisedStreet2': False, + 'foldToOtherRaisedStreet3': False, + 'foldToOtherRaisedStreet4': False, + 'foldToStreet1CBChance': False, + 'foldToStreet1CBDone': False, + 'foldToStreet2CBChance': False, + 'foldToStreet2CBDone': False, + 'foldToStreet3CBChance': False, + 'foldToStreet3CBDone': False, + 'foldToStreet4CBChance': False, + 'foldToStreet4CBDone': False, + 'foldedBbToSteal': False, + 'foldedSbToSteal': False, + 'other3BStreet0': False, + 'other4BStreet0': False, + 'otherRaisedStreet0': False, + 'otherRaisedStreet1': True, + 'otherRaisedStreet2': False, + 'otherRaisedStreet3': False, + 'otherRaisedStreet4': False, + 'position': 'B', + 'raiseFirstInChance': False, + 'raiseToStealChance': False, + 'raiseToStealDone': False, + 'raisedFirstIn': False, + 'rake': 0, + 'sawShowdown': False, + 'seatNo': 6, + 'sitout': False, + 'startCards': 0, + 'startCash': 1596850, + 'street0Aggr': False, + 'street0Bets': 0, + 'street0Calls': 1, + 'street0Raises': 0, + 'street0VPI': True, + 'street0_3BChance': True, + 'street0_3BDone': False, + 'street0_4BChance': False, + 'street0_4BDone': False, + 'street0_C4BChance': False, + 'street0_C4BDone': False, + 'street0_FoldTo3BChance': False, + 'street0_FoldTo3BDone': False, + 'street0_FoldTo4BChance': False, + 'street0_FoldTo4BDone': False, + 'street0_SqueezeChance': False, + 'street0_SqueezeDone': False, + 'street1Aggr': False, + 'street1Bets': 0, + 'street1CBChance': False, + 'street1CBDone': False, + 'street1Calls': 0, + 'street1CheckCallRaiseChance': False, + 'street1CheckCallRaiseDone': False, + 'street1Raises': 0, + 'street1Seen': True, + 'street2Aggr': False, + 'street2Bets': 0, + 'street2CBChance': False, + 'street2CBDone': False, + 'street2Calls': 0, + 'street2CheckCallRaiseChance': True, + 'street2CheckCallRaiseDone': False, + 'street2Raises': 0, + 'street2Seen': False, + 'street3Aggr': False, + 'street3Bets': 0, + 'street3CBChance': False, + 'street3CBDone': False, + 'street3Calls': 0, + 'street3CheckCallRaiseChance': False, + 'street3CheckCallRaiseDone': False, + 'street3Raises': 0, + 'street3Seen': False, + 'street4Aggr': False, + 'street4Bets': 0, + 'street4CBChance': False, + 'street4CBDone': False, + 'street4Calls': 0, + 'street4CheckCallRaiseChance': False, + 'street4CheckCallRaiseDone': False, + 'street4Raises': 0, + 'street4Seen': False, + 'success_Steal': False, + 'totalProfit': -40000, + 'tourneyTypeId': None, + 'tourneysPlayersIds': None, + 'winnings': 0, + 'wonAtSD': 0.0, + 'wonWhenSeenStreet1': 0.0, + 'wonWhenSeenStreet2': 0.0, + 'wonWhenSeenStreet3': 0.0, + 'wonWhenSeenStreet4': 0.0}, + u'TexasLimitDonk': { 'card1': 0, + 'card2': 0, + 'card3': 0, + 'card4': 0, + 'card5': 0, + 'card6': 0, + 'card7': 0, + 'foldBbToStealChance': False, + 'foldSbToStealChance': False, + 'foldToOtherRaisedStreet0': False, + 'foldToOtherRaisedStreet1': False, + 'foldToOtherRaisedStreet2': False, + 'foldToOtherRaisedStreet3': False, + 'foldToOtherRaisedStreet4': False, + 'foldToStreet1CBChance': False, + 'foldToStreet1CBDone': False, + 'foldToStreet2CBChance': False, + 'foldToStreet2CBDone': False, + 'foldToStreet3CBChance': False, + 'foldToStreet3CBDone': False, + 'foldToStreet4CBChance': False, + 'foldToStreet4CBDone': False, + 'foldedBbToSteal': False, + 'foldedSbToSteal': False, + 'other3BStreet0': False, + 'other4BStreet0': False, + 'otherRaisedStreet0': False, + 'otherRaisedStreet1': False, + 'otherRaisedStreet2': False, + 'otherRaisedStreet3': False, + 'otherRaisedStreet4': False, + 'position': 0, + 'raiseFirstInChance': False, + 'raiseToStealChance': False, + 'raiseToStealDone': False, + 'raisedFirstIn': False, + 'rake': 0, + 'sawShowdown': False, + 'seatNo': 3, + 'sitout': False, + 'startCards': 0, + 'startCash': 681600, + 'street0Aggr': False, + 'street0Bets': 0, + 'street0Calls': 0, + 'street0Raises': 0, + 'street0VPI': False, + 'street0_3BChance': True, + 'street0_3BDone': False, + 'street0_4BChance': False, + 'street0_4BDone': False, + 'street0_C4BChance': False, + 'street0_C4BDone': False, + 'street0_FoldTo3BChance': False, + 'street0_FoldTo3BDone': False, + 'street0_FoldTo4BChance': False, + 'street0_FoldTo4BDone': False, + 'street0_SqueezeChance': False, + 'street0_SqueezeDone': False, + 'street1Aggr': False, + 'street1Bets': 0, + 'street1CBChance': False, + 'street1CBDone': False, + 'street1Calls': 0, + 'street1CheckCallRaiseChance': False, + 'street1CheckCallRaiseDone': False, + 'street1Raises': 0, + 'street1Seen': False, + 'street2Aggr': False, + 'street2Bets': 0, + 'street2CBChance': False, + 'street2CBDone': False, + 'street2Calls': 0, + 'street2CheckCallRaiseChance': False, + 'street2CheckCallRaiseDone': False, + 'street2Raises': 0, + 'street2Seen': False, + 'street3Aggr': False, + 'street3Bets': 0, + 'street3CBChance': False, + 'street3CBDone': False, + 'street3Calls': 0, + 'street3CheckCallRaiseChance': False, + 'street3CheckCallRaiseDone': False, + 'street3Raises': 0, + 'street3Seen': False, + 'street4Aggr': False, + 'street4Bets': 0, + 'street4CBChance': False, + 'street4CBDone': False, + 'street4Calls': 0, + 'street4CheckCallRaiseChance': False, + 'street4CheckCallRaiseDone': False, + 'street4Raises': 0, + 'street4Seen': False, + 'success_Steal': False, + 'totalProfit': 0, + 'tourneyTypeId': None, + 'tourneysPlayersIds': None, + 'winnings': 0, + 'wonAtSD': 0.0, + 'wonWhenSeenStreet1': 0.0, + 'wonWhenSeenStreet2': 0.0, + 'wonWhenSeenStreet3': 0.0, + 'wonWhenSeenStreet4': 0.0}, + u'zbubop': { 'card1': 28, + 'card2': 8, + 'card3': 52, + 'card4': 50, + 'card5': 0, + 'card6': 0, + 'card7': 0, + 'foldBbToStealChance': False, + 'foldSbToStealChance': False, + 'foldToOtherRaisedStreet0': False, + 'foldToOtherRaisedStreet1': False, + 'foldToOtherRaisedStreet2': False, + 'foldToOtherRaisedStreet3': False, + 'foldToOtherRaisedStreet4': False, + 'foldToStreet1CBChance': False, + 'foldToStreet1CBDone': False, + 'foldToStreet2CBChance': False, + 'foldToStreet2CBDone': False, + 'foldToStreet3CBChance': False, + 'foldToStreet3CBDone': False, + 'foldToStreet4CBChance': False, + 'foldToStreet4CBDone': False, + 'foldedBbToSteal': False, + 'foldedSbToSteal': False, + 'other3BStreet0': False, + 'other4BStreet0': False, + 'otherRaisedStreet0': False, + 'otherRaisedStreet1': True, + 'otherRaisedStreet2': True, + 'otherRaisedStreet3': False, + 'otherRaisedStreet4': False, + 'position': 2, + 'raiseFirstInChance': True, + 'raiseToStealChance': False, + 'raiseToStealDone': False, + 'raisedFirstIn': False, + 'rake': 150, + 'sawShowdown': True, + 'seatNo': 1, + 'sitout': False, + 'startCards': 0, + 'startCash': 1067600, + 'street0Aggr': False, + 'street0Bets': 0, + 'street0Calls': 2, + 'street0Raises': 0, + 'street0VPI': True, + 'street0_3BChance': True, + 'street0_3BDone': False, + 'street0_4BChance': False, + 'street0_4BDone': False, + 'street0_C4BChance': False, + 'street0_C4BDone': False, + 'street0_FoldTo3BChance': False, + 'street0_FoldTo3BDone': False, + 'street0_FoldTo4BChance': False, + 'street0_FoldTo4BDone': False, + 'street0_SqueezeChance': True, + 'street0_SqueezeDone': False, + 'street1Aggr': False, + 'street1Bets': 0, + 'street1CBChance': False, + 'street1CBDone': False, + 'street1Calls': 1, + 'street1CheckCallRaiseChance': False, + 'street1CheckCallRaiseDone': False, + 'street1Raises': 0, + 'street1Seen': True, + 'street2Aggr': True, + 'street2Bets': 0, + 'street2CBChance': False, + 'street2CBDone': False, + 'street2Calls': 0, + 'street2CheckCallRaiseChance': True, + 'street2CheckCallRaiseDone': True, + 'street2Raises': 0, + 'street2Seen': True, + 'street3Aggr': True, + 'street3Bets': 1, + 'street3CBChance': True, + 'street3CBDone': True, + 'street3Calls': 0, + 'street3CheckCallRaiseChance': True, + 'street3CheckCallRaiseDone': True, + 'street3Raises': 0, + 'street3Seen': True, + 'street4Aggr': False, + 'street4Bets': 0, + 'street4CBChance': False, + 'street4CBDone': False, + 'street4Calls': 0, + 'street4CheckCallRaiseChance': False, + 'street4CheckCallRaiseDone': False, + 'street4Raises': 0, + 'street4Seen': False, + 'success_Steal': False, + 'totalProfit': 24850, + 'tourneyTypeId': None, + 'tourneysPlayersIds': None, + 'winnings': 204850, + 'wonAtSD': 1.0, + 'wonWhenSeenStreet1': 1.0, + 'wonWhenSeenStreet2': 1.0, + 'wonWhenSeenStreet3': 1.0, + 'wonWhenSeenStreet4': 0.0}} From f0e046ac83ffef85ada6cf0c9766c7aff476d878 Mon Sep 17 00:00:00 2001 From: gimick Date: Sat, 5 Mar 2011 18:17:46 +0000 Subject: [PATCH 04/23] exe: python 2.7 upgrade + cdecimal import --- .../MySqlPython1.2.3WalkthroughPython27.txt | 125 +++++ packaging/windows/py27-links.txt | 30 +- .../windows/py2exeWalkthroughPython27.txt | 241 ++++++++++ packaging/windows/py2exe_setup.py | 450 +++++++++--------- packaging/windows/pypoker138walkthrough.txt | 57 ++- 5 files changed, 648 insertions(+), 255 deletions(-) create mode 100644 packaging/windows/MySqlPython1.2.3WalkthroughPython27.txt create mode 100644 packaging/windows/py2exeWalkthroughPython27.txt diff --git a/packaging/windows/MySqlPython1.2.3WalkthroughPython27.txt b/packaging/windows/MySqlPython1.2.3WalkthroughPython27.txt new file mode 100644 index 00000000..224bf9f9 --- /dev/null +++ b/packaging/windows/MySqlPython1.2.3WalkthroughPython27.txt @@ -0,0 +1,125 @@ +Create MySqlPython version 1.2.3 windows installer for Python26 + +This walkthrough is derived from excellent installation instructions released under GNU Free Documentation License 1.2. The original work is here ... http://www.bcspcsonline.com/wiki/index.php?title=MySQL-5.1.34_Python-2.6_Module_Build_Instructions The Wiki author appears to be "Irondesk" + +This version by Gimick on 29th June 2010 +Content is available under GNU Free Documentation License 1.2 + +Premamble +--------- + + +The FPDB exe needs to build against the MySql-Python project. Unfortunately, for python 2.6 there is no official installer for windows, and none is ever likely to be provided. + +Community builds are available, but to reduce third-party dependencies, we will build our own here. + +Step 0 Get a fresh XP installation +---------------------------------- + +0.1/ Using XPhome 32bit + + +Step 1, VisualStudio 2008 express install +----------------------------------------- + +1.1/ Get the ISO CD from here ... http://www.microsoft.com/express/Downloads/#2008-All + +1.2/ Run and install Visual C++ only, don't bother with the additional packages offered + +This package will run 30 days before registration is needed + + +Step 2, setup Mysql Server +-------------------------- + +2.1/ Install MySQL server runtime ... http://downloads.mysql.com/archives/mysql-5.1/mysql-5.1.34-win32.msi + +Choose Typical, choose configure, choose Standard Configuration, choose all defaults, supply admin username/password. + + +Step 3, more installs +---------------------- + +3.1/ install the following in sequence (accept all default options) there should be no errors ! + +Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi +7zip 914 ... http://sourceforge.net/projects/sevenzip/files/7-Zip/9.14/7z914.exe/download + + +Step 4, grab Mysql server Source +-------------------------------- + +4.1/ Download ... http://downloads.mysql.com/archives/mysql-5.1/mysql-noinstall-5.1.34-win32.zip +4.2/ Unpacking Desktop\mysqlsource (use 7zip) +4.3/ Copy the following source directories to the MySql installation: + +dos> xcopy Desktop\mysqlsource\mysql-5.1.34-win32\data\* "c:\Program Files\MySQL\MySQL Server 5.1\data" /I/E/F/H +dos> xcopy Desktop\mysqlsource\mysql-5.1.34-win32\Embedded\* "c:\Program Files\MySQL\MySQL Server 5.1\Embedded" /I/E/F/H +dos> xcopy Desktop\mysqlsource\mysql-5.1.34-win32\include\* "c:\Program Files\MySQL\MySQL Server 5.1\include" /I/E/F/H +dos> xcopy Desktop\mysqlsource\mysql-5.1.34-win32\lib\* "c:\Program Files\MySQL\MySQL Server 5.1\lib" /I/E/F/H +dos> xcopy Desktop\mysqlsource\mysql-5.1.34-win32\mysql-test\* "c:\Program Files\MySQL\MySQL Server 5.1\mysql-test" /I/E/F/H +dos> xcopy Desktop\mysqlsource\mysql-5.1.34-win32\sql-bench\* "c:\Program Files\MySQL\MySQL Server 5.1\sql-bench" /I/E/F/H + +4.4/ You can delete Destop\mysqlsource, is no longer needed. + + +Step 5, grab Mysql-python source +-------------------------------- + +5.1/ get download +MySql for python ... http://sourceforge.net/projects/mysql-python/files/mysql-python/1.2.3/MySQL-python-1.2.3.tar.gz/download + +5.2/ extract MySQL-python-1.2.3 directory to the Desktop using 7zip +(note: use 7zip, open the gz, then open the tar, then extract the directory found inside there) + +Desktop\MySQL-python-1.2.3 should now exist + + +Step 6, get python build tools +------------------------------ + +6.1/ get Easy Setup installer +Easy setup installer ... http://peak.telecommunity.com/dist/ez_setup.py + +6.2/ Check the DEFAULT VERSION specified in Easy Setup and get the corresponding setuptools (version c11 in this case) +Setuptools version 11 ... http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg + +6.3/ Put both of these files into Desktop\MySQL-python-1.2.3, overwriting any existing files + + +Step 7, install the build tool +------------------------------ + +dos> cd Desktop\MySQL-python-1.2.3 +dos> c:\Python27\python.exe ez_setup.py setuptools-0.6c11-py2.7.egg + + +Step 8, Tweak the configuration +------------------------------- + +dos> cd Desktop\MySQL-python-1.2.3 + +8.1/ dos> write site.cfg + +Change registry_key = SOFTWARE\MySQL AB\MySQL Server 5.0 + to registry_key = SOFTWARE\MySQL AB\MySQL Server 5.1 + + +Step 9, build +------------- + +dos> cd Desktop\MySQL-python-1.2.3 + +9.1/ dos> c:\python27\python.exe setup.py build + +* Note: You will probably get a bunch of warnings and maybe a manifest error status 31, these are ok as long as there are no errors in compiling or linking. +* Note: This will generate the "MySQL-python-1.2.3/build" folder + +9.2/ dos> c:\python27\python.exe setup.py bdist_wininst + + +Step 10, done +------------- + +10.1/ the \dist directory will contain MySQL-python-1.2.3.win32-py2.7.exe !!!!! +10.2/ upload this file to sourceforge diff --git a/packaging/windows/py27-links.txt b/packaging/windows/py27-links.txt index cb1e413c..00f3cd81 100644 --- a/packaging/windows/py27-links.txt +++ b/packaging/windows/py27-links.txt @@ -1,16 +1,24 @@ -This is a list of download links of Windows packages for Python 2.7 of our dependencies. +This is a list of download links of Windows32 packages for Python 2.7 of our dependencies. -These are as of 26Feb2011: -matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/ -pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/ -pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/ -pyGobject X ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/ +These are as of 27Feb2011: - -The below are from Aug2010, and should probably be updated to newer versions: +Required: Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi -pywin 214 ... https://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/pywin32-214.win32-py2.7.exe/download -py2exe 0.6.9 ... https://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download -psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.2.win32-py2.7-pg8.4.4-release.exe +gtk+ allinone... http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.22/gtk+-bundle_2.22.1-20101227_win32.zip +matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/matplotlib-1.0.1.win32-py2.7.exe/download +pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-2.22.0-1.win32-py2.7.exe +pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/pycairo-1.8.10.win32-py2.7.exe +pyGobject X ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/pygobject-2.26.0-1.win32-py2.7.exe +pywin 216 ... http://sourceforge.net/projects/pywin32/files/pywin32/Build216/pywin32-216.win32-py2.7.exe/download +numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download +pypokereval 138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pokereval-138.win32-py2.7.exe/download + +optional: +cdecimal 2.2 ... http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.2.win32-py2.7.msi +mysql-python 1.2.3 ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3.win32-py2.7.exe/download +psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.3.1.win32-py2.7-pg9.0.1-release.exe + +Developers only: +py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download diff --git a/packaging/windows/py2exeWalkthroughPython27.txt b/packaging/windows/py2exeWalkthroughPython27.txt new file mode 100644 index 00000000..c1fe722c --- /dev/null +++ b/packaging/windows/py2exeWalkthroughPython27.txt @@ -0,0 +1,241 @@ +PY2EXE walkthrough for Python 2.7 & FPDB 0.2x +created on 27th Feb 2011 by Gimick + +This walkthrough is derived from comments in the py2exe script made by Ray and SqlCoder +Additional information, formatting, updating to Python 2.6 and Python 2.7 and sequencing added by Gimick +Content is available under the the GNU Affero General Public License version 3 + + + +Step 0 Get a fresh XP installation +---------------------------------- + +0.1/ Using XPhome or Pro 32bit + +0.2/ Ensure the CPU supports SSE2 instruction set or better. + + +Step 1, dependency install +-------------------------- + +1.1/ install the following in sequence (accept all default options) there should be no errors ! + +Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi +matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/matplotlib-1.0.1.win32-py2.7.exe/download +pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-2.22.0-1.win32-py2.7.exe +pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/pycairo-1.8.10.win32-py2.7.exe +pyGobject X 2.26 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/pygobject-2.26.0-1.win32-py2.7.exe +pywin 216 ... http://sourceforge.net/projects/pywin32/files/pywin32/Build216/pywin32-216.win32-py2.7.exe/download + +pypokereval 138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pokereval-138.win32-py2.7.exe/download +cdecimal 2.2 ... http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.2.win32-py2.7.msi +psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.1.win32-py2.6-pg8.4.3-release.exe + +(Note: stickpeople is the offical repository, not a community build) + +py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download + +1.2/ MySQL + +Install the following file: +mysql-python 1.2.3 ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3.win32-py2.7.exe/download + +1.3/ pytz fixup to work in an executable package + +pytz needs runtime access to timezone definition files. pytz is hard-coded to search in the directory from which the pytz .py modules are being run. +In a py2exe package, this directory is actually a library.zip container file, so windows cannot find the timezone definitions, and will crash the app. + +We need to make a one-line change to pytz to search in the current working directory (which is not a container), and not the application directory. +The py2exe script copies the timezone datafiles into the package folder pyfpdb/zoneinfo. + +Thanks to Jeff Peck gmail.com> on the py2exe mailing list for documenting this problem and solution. + +1.3.1/ Navigate to C:\Python27\Lib\site-packages\pytz +1.3.2/ Edit __init__.py +1.3.3/ At line 55 replace the following line(s): + + filename = os.path.join(os.path.dirname(__file__), + 'zoneinfo', *name_parts) + +with this line: + + filename = os.path.join(os.getcwd(), 'zoneinfo', *name_parts) + +1.3.4/ Save and exit + + +1.4/ Patch py2exe to stop popup runtime error message + +see http://www.py2exe.org/index.cgi/StderrLog for technical info. + +1.4.1/ + +dos> write C:\Python27\Lib\site-packages\py2exe\boot_common.py + +replace: + atexit.register(alert, 0, + "See the logfile '%s' for details" % fname, + "Errors occurred") +with: + #atexit.register(alert, 0, + # "See the logfile '%s' for details" % fname, + # "Errors occurred") + +1.4.2/ save and exit + + + +Step 2 Setup GTK +----------------- + +There are quite a few GTK packages needed, and rather than install them individually, I used the official AllinOne from the GTK project. + +2,1/ Create a new folder c:\GTK + +2.2/ Extract the following zip file into c:\GTK + +gtk+ allinone 2.22.1 ... http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.22/gtk+-bundle_2.22.1-20101227_win32.zip + +2.3/ If everything has worked, you should have c:\GTK\bin \etc \lib \src and so on created. + +2.4/ The /share/doc and /share/gtk-doc folders are huge, so can be emptied now (leave the /doc and /gtk-doc folders + in place, but delete the content) + + +Step 3 Set GTK into the PATH variable +------------------------------------- + +The path for GTK isn't set by default, so need to let the o/s know where the GTK stuff is. + +3.1/ Rightclick on mycomputer to show system properties +3.2/ select advanced/environment Variables +3.3/ in "system variables" NOT "user variables" do the following +3.3.1/ create a new item as name: GTK_BASEPATH value: c:\GTK +3.3.2/ edit the item "path", press home to get to the first character and insert the following text, (no quotes, including semicolon) %GTK_BASEPATH%\bin; + +3.4/ to check, open command prompt and do: + +dos>path ... system should respond with ... PATH=c:\GTK\bin;C:\WIN........ + +3.5/ Give it a spin to test (hopefully an application will start, if not, something has gone wrong) + +dos> gtk-demo + + +Step 4 Get the fpdb GIT tree +---------------------------- + +4.1/ Best to take a copy to work with; following steps will assume that the fpdb folder is on the Desktop +4.2/ Edit the script in packaging/windows/py2exe_setup.py to set the fpdbver variable for this release + + +5.3/ Install correct Numpy for this build +----------------------------------------- + +Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. + +For this reason, do not just run the installer downloaded. We will force a nosse version, to minimise problems on +older client PC's + +5.3.1/ download the package to the Desktop + +numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download + +5.3.3/ If you are wanting to build a package which works on all CPU's, install noSSE as follows: + +dos> cd Desktop +dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse + +5.3.4/ At the end of the installation, click on "show details" to confirm the installation. + +"Target CPU handles SSE2" +"Target CPU handles SSE3" +"nosse install (arch value: nosse)" +"Install NO SSE" +Extract: numpy-1.5.1-nosse.exe... 100% +Execute: "C:\DOCUME~1\user\LOCALS~1\Temp\numpy-1.5.1-nosse.exe" +Completed + +Step 6 Run py2exe to generate fpdb.exe +-------------------------------------- + +6.1/ Run the script to create the fpdb.exe bundle + +dos> cd Desktop\fpdb\packaging\windows +dos> c:\python27\python.exe py2exe_setup.py py2exe + +wait a while, watch lots of copying and whatever. + +6.2/ You should next get prompted for the GTK folder. +c:\GTK + +6.3/ If there are no errors reported, it has probably worked, we will test soon. + +Build notes: + +There is a warning about dll's not included "umath.pyd - c:\Python27\lib\site-packages\numpy\core\umath.pyd" + - reason for this is not understood at present. (Umath is apparently included in the built package). + + +Step 7 not currently used +------------------------- + +Has been deleted + + +Step 8 Drag out the completed bundle +------------------------------------ + +py2exe creates a new folder for the created software bundle, drag this out to the desktop for ease of working. + +8.1/ Drag Desktop\fpdb\packaging\windows\fpdb-n.nn.nnn to Desktop\ + + +Step 9 Initial run +------------------ + +9.1/ Open the Desktop\fpdb-n.nn.nnn folder +9.2/ In explorer...tools...folder options...View uncheck "Hide extensions for known file types" +9.3/ Double click run_fpdb.bat +9.4/ check the contents of pyfpdb\fpdb.exe.log, deal with any errors thrown + +9.5/ hopefully, fpdb will run +9.6/ Try out a few options, deal with any errors reported + +Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. + + +Step 11 pruning +--------------- + +11.1/ The generated folder is 100+MB and can be pruned by removing the following directories: + +pyfpdb/lib/glib-2.0 +pyfpdb/lib/pkgconfig +pyfpdb/share/aclocal +pyfpdb/share/doc +pyfpdb/share/glib-2.0 +pyfpdb/share/gtk-2.0 +pyfpdb/share/gtk-doc +pyfpdb/share/locale +pyfpdb/share/man + + +Step 12 rename folder +--------------------- + +If needed, rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. + + +Step 13 Compress to executable archive +-------------------------------------- + +13.1/ Download and install 7zip 914 ... http://sourceforge.net/projects/sevenzip/files/7-Zip/9.14/7z914.exe/download +13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch +13.3/ Test the created exe file + +Step 14 If you need to build again for a different SSE level +------------------------------------------------------------ + +Go back to step 5 and run again. + diff --git a/packaging/windows/py2exe_setup.py b/packaging/windows/py2exe_setup.py index 84b75886..25a54c79 100644 --- a/packaging/windows/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -1,225 +1,225 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""setup.py - -Py2exe script for fpdb. -""" -# Copyright 2009-2010, Ray E. Barker -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# 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 General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -######################################################################## - -#TODO: -# get rid of all the uneeded libraries (e.g., pyQT) -# think about an installer - -# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate -# include the lib needed to handle png files in mucked - -#HOW TO USE this script: -# -#- cd to the folder where this script is stored, usually ...packaging/windows -#- Run the script with python "py2exe_setup.py py2exe" -#- You will frequently get messages about missing .dll files.just assume other -# person will have them? we have copyright issues including some dll's -#- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should -# contain 2 dirs; gfx and pyfpdb and run_fpdb.bat -#- [ This bit is now automated: -# Last, you must copy the etc/, lib/ and share/ folders from your -# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders, -# not just the contents) ] -#- You can (should) then prune the etc/, lib/ and share/ folders to -# remove components we don't need. (see output at end of program run) - -# See walkthrough in packaging directory for versions used - -# steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree -# Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb - - -import os -import sys - -# get out now if parameter not passed -try: - sys.argv[1] <> "" -except: - print "A parameter is required, quitting now" - quit() - -from distutils.core import setup -import py2exe -import glob -import matplotlib -import shutil -#from datetime import date - -def isSystemDLL(pathname): - #dwmapi appears to be vista-specific file, not XP - if os.path.basename(pathname).lower() in ("dwmapi.dll"): - return 0 - return origIsSystemDLL(pathname) - -def test_and_remove(top): - if os.path.exists(top): - if os.path.isdir(top): - remove_tree(top) - else: - print "Unexpected file '"+top+"' found. Exiting." - exit() - -def remove_tree(top): - # Delete everything reachable from the directory named in 'top', - # assuming there are no symbolic links. - # CAUTION: This is dangerous! For example, if top == '/', it - # could delete all your disk files. - # sc: Nicked this from somewhere, added the if statement to try - # make it a bit safer - if top in ('build','dist',distdir) and os.path.basename(os.getcwd()) == 'windows': - #print "removing directory '"+top+"' ..." - for root, dirs, files in os.walk(top, topdown=False): - for name in files: - os.remove(os.path.join(root, name)) - for name in dirs: - os.rmdir(os.path.join(root, name)) - os.rmdir(top) - -def copy_tree(source,destination): - source = source.replace('\\', '\\\\') - destination = destination.replace('\\', '\\\\') - print "*** Copying " + source + " to " + destination + " ***" - shutil.copytree( source, destination ) - -def copy_file(source,destination): - source = source.replace('\\', '\\\\') - destination = destination.replace('\\', '\\\\') - print "*** Copying " + source + " to " + destination + " ***" - shutil.copy( source, destination ) - - -fpdbver = '0.21.rc1' - -distdir = r'fpdb-' + fpdbver -rootdir = r'../../' #cwd is normally /packaging/windows -pydir = rootdir+'pyfpdb/' -gfxdir = rootdir+'gfx/' -sys.path.append( pydir ) # allows fpdb modules to be found by options/includes below - - -print "\n" + r"Output will be created in "+distdir - -print "*** Cleaning working folders ***" -test_and_remove('dist') -test_and_remove('build') -test_and_remove(distdir) - -print "*** Building now in dist folder ***" - -origIsSystemDLL = py2exe.build_exe.isSystemDLL -py2exe.build_exe.isSystemDLL = isSystemDLL - -setup( - name = 'fpdb', - description = 'Free Poker DataBase', - version = fpdbver, - - windows = [ {'script': pydir+'fpdb.pyw', "icon_resources": [(1, gfxdir+"fpdb_large_icon.ico")]}, - {'script': pydir+'HUD_main.pyw', }, - {'script': pydir+'Configuration.py', } - ], - - console = [ {'script': pydir+'Stove.py', } - ], - - options = {'py2exe': { - 'packages' : ['encodings', 'matplotlib'], - 'includes' : ['gio', 'cairo', 'pango', 'pangocairo', 'atk', 'gobject' - ,'matplotlib.numerix.random_array' - ,'AbsoluteToFpdb', 'BetfairToFpdb' - ,'CarbonToFpdb', 'EverleafToFpdb' - ,'FulltiltToFpdb', 'iPokerToFpdb' - ,'OnGameToFpdb', 'PartyPokerToFpdb' - ,'PkrToFpdb', 'PokerStarsToFpdb' - ,'Win2dayToFpdb', 'WinamaxToFpdb' - ], - 'excludes' : ['_tkagg', '_agg2', 'cocoaagg', 'fltkagg'], - 'dll_excludes': ['libglade-2.0-0.dll', 'libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll' - , 'msvcr90.dll', 'MSVCP90.dll', 'MSVCR90.dll','msvcr90.dll'], # these are vis c / c++ runtimes, and must not be redistributed - } - }, - - # files in 2nd value in tuple are moved to dir named in 1st value - # this code will not walk a tree - # Note: cwd for 1st value is packaging/windows/dist (this is confusing BTW) - # Note: only include files here which are to be put into the package pyfpdb folder or subfolders - - data_files = [('', glob.glob(rootdir+'*.txt')) - ,('', [pydir+'HUD_config.xml.example',pydir+'Cards01.png', pydir+'logging.conf']) - ] + matplotlib.get_py2exe_datafiles() -) - -# ,(distdir, [rootdir+'run_fpdb.bat']) -# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) -# ] + -print "*** py2exe build phase complete ***" - -# copy zone info and fpdb translation folders -copy_tree (r'c:\python26\Lib\site-packages\pytz\zoneinfo', os.path.join(r'dist', 'zoneinfo')) -copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) - -# create distribution folder and populate with gfx + bat -copy_tree (gfxdir, os.path.join(distdir, 'gfx')) -copy_file (rootdir+'run_fpdb.bat', distdir) - -print "*** Renaming dist folder as distribution pyfpdb folder ***" -dest = os.path.join(distdir, 'pyfpdb') -os.rename( 'dist', dest ) - -print "*** copying GTK runtime ***" -gtk_dir = "" -while not os.path.exists(gtk_dir): - print "Enter directory name for GTK (e.g. c:/gtk) : ", # the comma means no newline - gtk_dir = sys.stdin.readline().rstrip() - -print "*** copying GTK runtime ***" -dest = os.path.join(distdir, 'pyfpdb') -copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) -copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) -copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) -copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) -copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) - -print "*** All done! ***" -test_and_remove('build') -print distdir+" is in the pyfpdb dir" -print """ -The following dirs can probably removed to make the final package smaller: - -pyfpdb/lib/glib-2.0 -pyfpdb/lib/gtk-2.0/include -pyfpdb/lib/pkgconfig -pyfpdb/share/aclocal -pyfpdb/share/doc -pyfpdb/share/glib-2.0 -pyfpdb/share/gtk-2.0 -pyfpdb/share/gtk-doc -pyfpdb/share/locale -pyfpdb/share/man -pyfpdb/share/themes/Default - -Use 7-zip to zip up the distribution and create a self extracting archive and that's it! -""" +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""setup.py + +Py2exe script for fpdb. +""" +# Copyright 2009-2010, Ray E. Barker +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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 General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +######################################################################## + +#TODO: +# get rid of all the uneeded libraries (e.g., pyQT) +# think about an installer + +# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate +# include the lib needed to handle png files in mucked + +#HOW TO USE this script: +# +#- cd to the folder where this script is stored, usually ...packaging/windows +#- Run the script with python "py2exe_setup.py py2exe" +#- You will frequently get messages about missing .dll files.just assume other +# person will have them? we have copyright issues including some dll's +#- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should +# contain 2 dirs; gfx and pyfpdb and run_fpdb.bat +#- [ This bit is now automated: +# Last, you must copy the etc/, lib/ and share/ folders from your +# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders, +# not just the contents) ] +#- You can (should) then prune the etc/, lib/ and share/ folders to +# remove components we don't need. (see output at end of program run) + +# See walkthrough in packaging directory for versions used + +# steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree +# Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb + + +import os +import sys + +# get out now if parameter not passed +try: + sys.argv[1] <> "" +except: + print "A parameter is required, quitting now" + quit() + +from distutils.core import setup +import py2exe +import glob +import matplotlib +import shutil +import cdecimal + +def isSystemDLL(pathname): + #dwmapi appears to be vista-specific file, not XP + if os.path.basename(pathname).lower() in ("dwmapi.dll"): + return 0 + return origIsSystemDLL(pathname) + +def test_and_remove(top): + if os.path.exists(top): + if os.path.isdir(top): + remove_tree(top) + else: + print "Unexpected file '"+top+"' found. Exiting." + exit() + +def remove_tree(top): + # Delete everything reachable from the directory named in 'top', + # assuming there are no symbolic links. + # CAUTION: This is dangerous! For example, if top == '/', it + # could delete all your disk files. + # sc: Nicked this from somewhere, added the if statement to try + # make it a bit safer + if top in ('build','dist',distdir) and os.path.basename(os.getcwd()) == 'windows': + #print "removing directory '"+top+"' ..." + for root, dirs, files in os.walk(top, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(top) + +def copy_tree(source,destination): + source = source.replace('\\', '\\\\') + destination = destination.replace('\\', '\\\\') + print "*** Copying " + source + " to " + destination + " ***" + shutil.copytree( source, destination ) + +def copy_file(source,destination): + source = source.replace('\\', '\\\\') + destination = destination.replace('\\', '\\\\') + print "*** Copying " + source + " to " + destination + " ***" + shutil.copy( source, destination ) + + +fpdbver = '0.21' + +distdir = r'fpdb-' + fpdbver +rootdir = r'../../' #cwd is normally /packaging/windows +pydir = rootdir+'pyfpdb/' +gfxdir = rootdir+'gfx/' +sys.path.append( pydir ) # allows fpdb modules to be found by options/includes below + + +print "\n" + r"Output will be created in "+distdir + +print "*** Cleaning working folders ***" +test_and_remove('dist') +test_and_remove('build') +test_and_remove(distdir) + +print "*** Building now in dist folder ***" + +origIsSystemDLL = py2exe.build_exe.isSystemDLL +py2exe.build_exe.isSystemDLL = isSystemDLL + +setup( + name = 'fpdb', + description = 'Free Poker DataBase', + version = fpdbver, + + windows = [ {'script': pydir+'fpdb.pyw', "icon_resources": [(1, gfxdir+"fpdb_large_icon.ico")]}, + {'script': pydir+'HUD_main.pyw', }, + {'script': pydir+'Configuration.py', } + ], + + console = [ {'script': pydir+'Stove.py', } + ], + + options = {'py2exe': { + 'packages' : ['encodings', 'matplotlib'], + 'includes' : ['gio', 'cairo', 'pango', 'pangocairo', 'atk', 'gobject' + ,'matplotlib.numerix.random_array' + ,'AbsoluteToFpdb', 'BetfairToFpdb' + ,'CarbonToFpdb', 'EverleafToFpdb' + ,'FulltiltToFpdb', 'iPokerToFpdb' + ,'OnGameToFpdb', 'PartyPokerToFpdb' + ,'PkrToFpdb', 'PokerStarsToFpdb' + ,'Win2dayToFpdb', 'WinamaxToFpdb' + ], + 'excludes' : ['_tkagg', '_agg2', 'cocoaagg', 'fltkagg'], + 'dll_excludes': ['libglade-2.0-0.dll', 'libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll' + , 'msvcr90.dll', 'MSVCP90.dll', 'MSVCR90.dll','msvcr90.dll'], # these are vis c / c++ runtimes, and must not be redistributed + } + }, + + # files in 2nd value in tuple are moved to dir named in 1st value + # this code will not walk a tree + # Note: cwd for 1st value is packaging/windows/dist (this is confusing BTW) + # Note: only include files here which are to be put into the package pyfpdb folder or subfolders + + data_files = [('', glob.glob(rootdir+'*.txt')) + ,('', [pydir+'HUD_config.xml.example',pydir+'Cards01.png', pydir+'logging.conf']) + ] + matplotlib.get_py2exe_datafiles() +) + +# ,(distdir, [rootdir+'run_fpdb.bat']) +# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) +# ] + +print "*** py2exe build phase complete ***" + +# copy zone info and fpdb translation folders +copy_tree (r'c:\python27\Lib\site-packages\pytz\zoneinfo', os.path.join(r'dist', 'zoneinfo')) +copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) + +# create distribution folder and populate with gfx + bat +copy_tree (gfxdir, os.path.join(distdir, 'gfx')) +copy_file (rootdir+'run_fpdb.bat', distdir) + +print "*** Renaming dist folder as distribution pyfpdb folder ***" +dest = os.path.join(distdir, 'pyfpdb') +os.rename( 'dist', dest ) + +print "*** copying GTK runtime ***" +gtk_dir = "" +while not os.path.exists(gtk_dir): + print "Enter directory name for GTK (e.g. c:/gtk) : ", # the comma means no newline + gtk_dir = sys.stdin.readline().rstrip() + +print "*** copying GTK runtime ***" +dest = os.path.join(distdir, 'pyfpdb') +copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) +copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) +copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) +copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) +copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) + +print "*** All done! ***" +test_and_remove('build') +print distdir+" is in the pyfpdb dir" +print """ +The following dirs can probably removed to make the final package smaller: + +pyfpdb/lib/glib-2.0 +pyfpdb/lib/gtk-2.0/include +pyfpdb/lib/pkgconfig +pyfpdb/share/aclocal +pyfpdb/share/doc +pyfpdb/share/glib-2.0 +pyfpdb/share/gtk-2.0 +pyfpdb/share/gtk-doc +pyfpdb/share/locale +pyfpdb/share/man +pyfpdb/share/themes/Default + +Use 7-zip to zip up the distribution and create a self extracting archive and that's it! +""" diff --git a/packaging/windows/pypoker138walkthrough.txt b/packaging/windows/pypoker138walkthrough.txt index 1c7ee0f4..f4e5f3af 100644 --- a/packaging/windows/pypoker138walkthrough.txt +++ b/packaging/windows/pypoker138walkthrough.txt @@ -2,6 +2,7 @@ pypokereval build for windows stepbystep guide ---------------------------------------------- Created by Gimick on 3rd December 2010 +Updated for py27 by Gimick 27th Feb 2011 This walkthrough is derived with the assistance of EricBlade and the build notes supplied by Loic Dachary http://dachary.org/ @@ -12,7 +13,7 @@ Content is available under the the GNU Affero General Public License version 3 0. Build environ ---------------- -We are building against the 2008 runtime because Python 2.6 +We are building against the 2008 runtime because Python 2.7 has the same dependency (msvcr90.dll version 9.0.21022.8) Using winXPhome 32 bit @@ -31,7 +32,7 @@ This package will run 30 days before registration is needed 2.1/ Install python runtime from here ... -Python 2.6.5 ... http://www.python.org/ftp/python/2.6.5/python-2.6.5.msi +Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi 3. Source install ----------------- @@ -62,11 +63,29 @@ change this: to be this: -#define VERSION_NAME(W) W##2_6 -#define PYTHON_VERSION "2_6" +#define VERSION_NAME(W) W##2_7 +#define PYTHON_VERSION "2_7" + +- - - - - - + +Move this block of code: + +#ifdef _DEBUG // for Windows python23_d.lib is not in distribution... ugly but works + #undef _DEBUG + #include + #define _DEBUG +#else + #include +#endif + +To be after this line: + +#include "enumdefs.h" 4.2/ save and exit +- - - - - + 4.3/ dos> write c:/pypoker-eval/pokereval.py Comment-out this line: @@ -75,7 +94,7 @@ _pokereval = __import__('_pokereval_' + sys.version[0] + '_' + sys.version[2]) Insert this one in its' place: -import _pokereval_2_6 as _pokereval +import _pokereval_2_7 as _pokereval 4.4/ save and exit @@ -112,7 +131,7 @@ import _pokereval_2_6 as _pokereval 6.2.5 In the pythonpoker-eval properties dialog, -change references to "python24" to "python26" in the following: +change references to "python24" to "python27" in the following: = C/C++/Additional Include Directories/ = linker/general/Additional library directories @@ -121,7 +140,6 @@ change references to "python24" to "python26" in the following: Change the following = linker/generate debug info - set to No - = linker/debugging/Generate debug info - set to No 6.2.6 Apply all changes to the properties dialog and close @@ -154,38 +172,39 @@ Change the following 8.4 Exit from visual studio -9. packaging ------------- +9. Wrap-up +---------- 9.1 Navigate to c:/pypoker-eval/release 9.2 the output file is pypokereval.dll -9.3 rename this file to _pokereval_2_6.pyd +9.3 rename this file to _pokereval_2_7.pyd 9.4 create a zip file containing : -_pokereval_2_6.pyd from releases +_pokereval_2_7.pyd from releases test.py from pypoker-eval-138.0 pokereval.py from pypoker-eval-138.0 poker-eval.vcproj from c:\poker-eval pypoker-eval.vcproj from c:\pypoker-eval pypokereval.c from c:\pypoker-eval -Remember to include the version (138), python 265 and win32 in the package filename +Remember to include the version (138), python 27 and win32 in the package filename -10. Installation and Testing ----------------------------- +10. Testing +----------- -Python 2.6.5 must be installed +Python 2.7 must be installed -10.1 Extract this package to directory +10.1 Extract the zip file created in 9.4 into a new directory 10.2 Change directory to the directory in 10.1 -10.3 execute dos> c:\Python26\python.exe test.py +10.3 execute dos> c:\Python27\python.exe test.py 10.4 hand-output should scroll down the screen 10.5 start the python interpreter 10.6 >>> import pokereval 10.7 No errors should be seen +11. Packaging +------------- - - +Please follow pypokereval-win32-packaging-walkthrough.txt in the same directory as this walkthrough. From fe5dcb579eee358ffd812ea9ccb8b0e854edaeb7 Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 6 Mar 2011 09:40:49 +0800 Subject: [PATCH 05/23] Regression: Party freeroll tourney, sample --- .../NLHE-Freeroll-MTT-201008.Sample.hand.txt | Bin 0 -> 1368 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyfpdb/regression-test-files/tour/PartyPoker/Flop/NLHE-Freeroll-MTT-201008.Sample.hand.txt diff --git a/pyfpdb/regression-test-files/tour/PartyPoker/Flop/NLHE-Freeroll-MTT-201008.Sample.hand.txt b/pyfpdb/regression-test-files/tour/PartyPoker/Flop/NLHE-Freeroll-MTT-201008.Sample.hand.txt new file mode 100644 index 0000000000000000000000000000000000000000..c6075c9e1cb7883e3a61f14ed600ada4f3913012 GIT binary patch literal 1368 zcmb7^Pfy!G5XF18s(ro$W?HT%$dZ#h%JK}C{GK-xM8LO3$jXf3@Hh<*@C8$5 zTV1hrfUoUsYb?SdKs25t;Uvj$`eQmn97dt<&Dgx*_#B0F;gsnL*R82>ra9NDX)ub$ zX->ZbJXh`&^iN0EoHh8RBwru+-jp+o48p+H9P`#%B?+u7)yQ@^=Nf8(Rm0X?8*-#L zjzYY&6%+jKg$N@gK{tdW=1z*Yq{vA$%A2mtN$Hl9I4RqbVkhNWQtG5)D807`-`m(k zw{d(YvxK}wr5+?ove@A8(D%B>7FN9)a085M%xe7gy=PMrl&WniNM)h9St-hmkg#IO zk4BX+rIM1DmRIDN>sU}$jJwUDnK0bo<|KTjW;;_eO6h=F>>zrFRfO`kt z8R}Zx(@WG(ReXqa(>R`~)dQVDxeEGVF|3&AU&pC Date: Sun, 6 Mar 2011 09:41:43 +0800 Subject: [PATCH 06/23] Party: Fix Party freeroll tourney parsing --- pyfpdb/PartyPokerToFpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/PartyPokerToFpdb.py b/pyfpdb/PartyPokerToFpdb.py index 2abd744c..31cd3c3f 100755 --- a/pyfpdb/PartyPokerToFpdb.py +++ b/pyfpdb/PartyPokerToFpdb.py @@ -336,7 +336,7 @@ class PartyPoker(HandHistoryConverter): hand.fee = 0 hand.buyinCurrency = "FREE" hand.isKO = False - if hand.tourNo != None: + elif hand.tourNo != None: hand.buyin = 0 hand.fee = 0 hand.buyinCurrency = "FREE" From 8792b427dece82384cd7d0c3dae080b648c64ce7 Mon Sep 17 00:00:00 2001 From: gimick Date: Sun, 6 Mar 2011 23:51:21 +0000 Subject: [PATCH 07/23] fpdb.pyw: force bg_colour for inactive tab labels - workaround themes problem --- pyfpdb/fpdb.pyw | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyfpdb/fpdb.pyw b/pyfpdb/fpdb.pyw index b5501e35..c6d318a2 100755 --- a/pyfpdb/fpdb.pyw +++ b/pyfpdb/fpdb.pyw @@ -187,6 +187,22 @@ class fpdb: tabBox.pack_start(tabLabel, False) eventBox.add(tabBox) + # fixme: force background state to fix problem where STATE_ACTIVE + # tab labels are black in some gtk themes, and therefore unreadable + # This behaviour is probably a bug in libwimp.dll or pygtk, but + # need to force background to avoid issues with menu labels being + # unreadable + # + # gtk.STATE_ACTIVE is a displayed, but not selected tab + # gtk.STATE_NORMAL is a displayed, selected, focussed tab + # gtk.STATE_INSENSITIVE is an inactive tab + # Insensitive/base is chosen as the background colour, because + # although not perfect, it seems to be the least instrusive. + baseNormStyle = eventBox.get_style().base[gtk.STATE_INSENSITIVE] + if baseNormStyle: + print baseNormStyle + eventBox.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.color_parse(str(baseNormStyle))) + if nb.get_n_pages() > 0: tabButton = gtk.Button() From e0124d2d11c0cf20967532df68675c0cabee1491 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 10 Mar 2011 18:01:25 +0800 Subject: [PATCH 08/23] Database: Reformat a some tournament results update code Reformat to make life easier when debugging --- pyfpdb/Database.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 9812300b..c4fbb138 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -2359,8 +2359,9 @@ class Database: def createOrUpdateTourney(self, hand, source):#note: this method is used on Hand and TourneySummary objects cursor = self.get_cursor() - cursor.execute (self.sql.query['getTourneyByTourneyNo'].replace('%s', self.sql.query['placeholder']), - (hand.siteId, hand.tourNo)) + q = self.sql.query['getTourneyByTourneyNo'].replace('%s', self.sql.query['placeholder']) + cursor.execute(q, (hand.siteId, hand.tourNo)) + columnNames=[desc[0] for desc in cursor.description] result=cursor.fetchone() @@ -2385,9 +2386,12 @@ class Database: # if (resultDict[ev] < hand.startTime): # hand.startTime=resultDict[ev] if updateDb: - cursor.execute (self.sql.query['updateTourney'].replace('%s', self.sql.query['placeholder']), - (hand.entries, hand.prizepool, hand.startTime, hand.endTime, hand.tourneyName, - hand.matrixIdProcessed, hand.totalRebuyCount, hand.totalAddOnCount, hand.comment, hand.commentTs, tourneyId)) + q = self.sql.query['updateTourney'].replace('%s', self.sql.query['placeholder']) + row = (hand.entries, hand.prizepool, hand.startTime, hand.endTime, hand.tourneyName, + hand.matrixIdProcessed, hand.totalRebuyCount, hand.totalAddOnCount, hand.comment, + hand.commentTs, tourneyId + ) + cursor.execute(q, row) else: if source=="HHC": cursor.execute (self.sql.query['insertTourney'].replace('%s', self.sql.query['placeholder']), @@ -2436,9 +2440,19 @@ class Database: elif getattr(hand, handAttribute)[player]!=None and resultDict[ev]==None:#object has this value but DB doesnt, so update DB updateDb=True if updateDb: - cursor.execute (self.sql.query['updateTourneysPlayer'].replace('%s', self.sql.query['placeholder']), - (hand.ranks[player], hand.winnings[player], hand.winningsCurrency[player], - hand.rebuyCounts[player], hand.addOnCounts[player], hand.koCounts[player], tourneysPlayersIds[player[1]])) + q = self.sql.query['updateTourneysPlayer'].replace('%s', self.sql.query['placeholder']) + inputs = (hand.ranks[player], + hand.winnings[player], + hand.winningsCurrency[player], + hand.rebuyCounts[player], + hand.addOnCounts[player], + hand.koCounts[player], + tourneysPlayersIds[player[1]] + ) + #print q + #pp = pprint.PrettyPrinter(indent=4) + #pp.pprint(inputs) + cursor.execute(q, inputs) else: if source=="HHC": cursor.execute (self.sql.query['insertTourneysPlayer'].replace('%s', self.sql.query['placeholder']), From 07d2d5733c0c80fd79eedb20ae3f35fc7d018639 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 10 Mar 2011 18:03:21 +0800 Subject: [PATCH 09/23] TourneySummary: SQLite complaining about Decimal format. Variables were already passed in as int - allow them to remain so --- pyfpdb/TourneySummary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyfpdb/TourneySummary.py b/pyfpdb/TourneySummary.py index 5d44fc86..813d309d 100644 --- a/pyfpdb/TourneySummary.py +++ b/pyfpdb/TourneySummary.py @@ -245,13 +245,13 @@ class TourneySummary(object): Adds a player to the tourney, and initialises data structures indexed by player. rank (int) indicating the finishing rank (can be -1 if unknown) name (string) player name -winnings (decimal) the money the player ended the tourney with (can be 0, or -1 if unknown) +winnings (int) the money the player ended the tourney with (can be 0, or -1 if unknown) """ log.debug(_("addPlayer: rank:%s - name : '%s' - Winnings (%s)") % (rank, name, winnings)) self.players.append(name) if rank: - self.ranks.update( { name : Decimal(rank) } ) - self.winnings.update( { name : Decimal(winnings) } ) + self.ranks.update( { name : rank } ) + self.winnings.update( { name : winnings } ) self.winningsCurrency.update( { name : winningsCurrency } ) else: self.ranks.update( { name : None } ) From 095565658533fc262248f947bc086ef6d195aab2 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 10 Mar 2011 18:05:36 +0800 Subject: [PATCH 10/23] TSI: Add Winamax lines --- pyfpdb/TestSummaryImport.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyfpdb/TestSummaryImport.py b/pyfpdb/TestSummaryImport.py index 2aa2a81b..56264f0b 100755 --- a/pyfpdb/TestSummaryImport.py +++ b/pyfpdb/TestSummaryImport.py @@ -138,14 +138,14 @@ def main(argv=None): #CarbonErrors = FpdbError('Carbon') #PKRErrors = FpdbError('PKR') #iPokerErrors = FpdbError('iPoker') - #WinamaxErrors = FpdbError('Winamax') + WinamaxErrors = FpdbError('Winamax') ErrorsList = [ - PokerStarsErrors, - FTPErrors, #PartyPokerErrors, + PokerStarsErrors, FTPErrors, WinamaxErrors, + #PartyPokerErrors, #BetfairErrors, OnGameErrors, AbsoluteErrors, #EverleafErrors, CarbonErrors, PKRErrors, - #iPokerErrors, WinamaxErrors, UltimateBetErrors, + #iPokerErrors, UltimateBetErrors, ] sites = { @@ -160,7 +160,7 @@ def main(argv=None): #'Carbon' : True, #'PKR' : False, #'iPoker' : True, - #'Winamax' : True, + 'Winamax' : True, } if sites['PokerStars'] == True: @@ -187,8 +187,8 @@ def main(argv=None): # walk_testfiles("regression-test-files/cash/PKR/", compare, importer, PKRErrors, "PKR") #if sites['iPoker'] == True: # walk_testfiles("regression-test-files/cash/iPoker/", compare, importer, iPokerErrors, "iPoker") - #if sites['Winamax'] == True: - # walk_testfiles("regression-test-files/cash/Winamax/", compare, importer, WinamaxErrors, "Winamax") + if sites['Winamax'] == True: + walk_testfiles("regression-test-files/summaries/Winamax/", compare, importer, WinamaxErrors, "Winamax") totalerrors = 0 From 0dfe6b3d78cb6e220b3ecd546ef5abb0b0b3f169 Mon Sep 17 00:00:00 2001 From: gimick Date: Thu, 10 Mar 2011 21:02:14 +0000 Subject: [PATCH 11/23] py2exe: add more build automation, activate MS-Windows GTK theme --- .../windows/py2exeWalkthroughPython27.txt | 472 +++++++++--------- packaging/windows/py2exe_setup.py | 73 ++- 2 files changed, 267 insertions(+), 278 deletions(-) diff --git a/packaging/windows/py2exeWalkthroughPython27.txt b/packaging/windows/py2exeWalkthroughPython27.txt index c1fe722c..af498584 100644 --- a/packaging/windows/py2exeWalkthroughPython27.txt +++ b/packaging/windows/py2exeWalkthroughPython27.txt @@ -1,241 +1,231 @@ -PY2EXE walkthrough for Python 2.7 & FPDB 0.2x -created on 27th Feb 2011 by Gimick - -This walkthrough is derived from comments in the py2exe script made by Ray and SqlCoder -Additional information, formatting, updating to Python 2.6 and Python 2.7 and sequencing added by Gimick -Content is available under the the GNU Affero General Public License version 3 - - - -Step 0 Get a fresh XP installation ----------------------------------- - -0.1/ Using XPhome or Pro 32bit - -0.2/ Ensure the CPU supports SSE2 instruction set or better. - - -Step 1, dependency install --------------------------- - -1.1/ install the following in sequence (accept all default options) there should be no errors ! - -Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi -matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/matplotlib-1.0.1.win32-py2.7.exe/download -pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-2.22.0-1.win32-py2.7.exe -pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/pycairo-1.8.10.win32-py2.7.exe -pyGobject X 2.26 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/pygobject-2.26.0-1.win32-py2.7.exe -pywin 216 ... http://sourceforge.net/projects/pywin32/files/pywin32/Build216/pywin32-216.win32-py2.7.exe/download - -pypokereval 138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pokereval-138.win32-py2.7.exe/download -cdecimal 2.2 ... http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.2.win32-py2.7.msi -psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.1.win32-py2.6-pg8.4.3-release.exe - -(Note: stickpeople is the offical repository, not a community build) - -py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download - -1.2/ MySQL - -Install the following file: -mysql-python 1.2.3 ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3.win32-py2.7.exe/download - -1.3/ pytz fixup to work in an executable package - -pytz needs runtime access to timezone definition files. pytz is hard-coded to search in the directory from which the pytz .py modules are being run. -In a py2exe package, this directory is actually a library.zip container file, so windows cannot find the timezone definitions, and will crash the app. - -We need to make a one-line change to pytz to search in the current working directory (which is not a container), and not the application directory. -The py2exe script copies the timezone datafiles into the package folder pyfpdb/zoneinfo. - -Thanks to Jeff Peck gmail.com> on the py2exe mailing list for documenting this problem and solution. - -1.3.1/ Navigate to C:\Python27\Lib\site-packages\pytz -1.3.2/ Edit __init__.py -1.3.3/ At line 55 replace the following line(s): - - filename = os.path.join(os.path.dirname(__file__), - 'zoneinfo', *name_parts) - -with this line: - - filename = os.path.join(os.getcwd(), 'zoneinfo', *name_parts) - -1.3.4/ Save and exit - - -1.4/ Patch py2exe to stop popup runtime error message - -see http://www.py2exe.org/index.cgi/StderrLog for technical info. - -1.4.1/ - -dos> write C:\Python27\Lib\site-packages\py2exe\boot_common.py - -replace: - atexit.register(alert, 0, - "See the logfile '%s' for details" % fname, - "Errors occurred") -with: - #atexit.register(alert, 0, - # "See the logfile '%s' for details" % fname, - # "Errors occurred") - -1.4.2/ save and exit - - - -Step 2 Setup GTK ------------------ - -There are quite a few GTK packages needed, and rather than install them individually, I used the official AllinOne from the GTK project. - -2,1/ Create a new folder c:\GTK - -2.2/ Extract the following zip file into c:\GTK - -gtk+ allinone 2.22.1 ... http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.22/gtk+-bundle_2.22.1-20101227_win32.zip - -2.3/ If everything has worked, you should have c:\GTK\bin \etc \lib \src and so on created. - -2.4/ The /share/doc and /share/gtk-doc folders are huge, so can be emptied now (leave the /doc and /gtk-doc folders - in place, but delete the content) - - -Step 3 Set GTK into the PATH variable -------------------------------------- - -The path for GTK isn't set by default, so need to let the o/s know where the GTK stuff is. - -3.1/ Rightclick on mycomputer to show system properties -3.2/ select advanced/environment Variables -3.3/ in "system variables" NOT "user variables" do the following -3.3.1/ create a new item as name: GTK_BASEPATH value: c:\GTK -3.3.2/ edit the item "path", press home to get to the first character and insert the following text, (no quotes, including semicolon) %GTK_BASEPATH%\bin; - -3.4/ to check, open command prompt and do: - -dos>path ... system should respond with ... PATH=c:\GTK\bin;C:\WIN........ - -3.5/ Give it a spin to test (hopefully an application will start, if not, something has gone wrong) - -dos> gtk-demo - - -Step 4 Get the fpdb GIT tree ----------------------------- - -4.1/ Best to take a copy to work with; following steps will assume that the fpdb folder is on the Desktop -4.2/ Edit the script in packaging/windows/py2exe_setup.py to set the fpdbver variable for this release - - -5.3/ Install correct Numpy for this build ------------------------------------------ - -Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. - -For this reason, do not just run the installer downloaded. We will force a nosse version, to minimise problems on -older client PC's - -5.3.1/ download the package to the Desktop - -numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download - -5.3.3/ If you are wanting to build a package which works on all CPU's, install noSSE as follows: - -dos> cd Desktop -dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse - -5.3.4/ At the end of the installation, click on "show details" to confirm the installation. - -"Target CPU handles SSE2" -"Target CPU handles SSE3" -"nosse install (arch value: nosse)" -"Install NO SSE" -Extract: numpy-1.5.1-nosse.exe... 100% -Execute: "C:\DOCUME~1\user\LOCALS~1\Temp\numpy-1.5.1-nosse.exe" -Completed - -Step 6 Run py2exe to generate fpdb.exe --------------------------------------- - -6.1/ Run the script to create the fpdb.exe bundle - -dos> cd Desktop\fpdb\packaging\windows -dos> c:\python27\python.exe py2exe_setup.py py2exe - -wait a while, watch lots of copying and whatever. - -6.2/ You should next get prompted for the GTK folder. -c:\GTK - -6.3/ If there are no errors reported, it has probably worked, we will test soon. - -Build notes: - -There is a warning about dll's not included "umath.pyd - c:\Python27\lib\site-packages\numpy\core\umath.pyd" - - reason for this is not understood at present. (Umath is apparently included in the built package). - - -Step 7 not currently used -------------------------- - -Has been deleted - - -Step 8 Drag out the completed bundle ------------------------------------- - -py2exe creates a new folder for the created software bundle, drag this out to the desktop for ease of working. - -8.1/ Drag Desktop\fpdb\packaging\windows\fpdb-n.nn.nnn to Desktop\ - - -Step 9 Initial run ------------------- - -9.1/ Open the Desktop\fpdb-n.nn.nnn folder -9.2/ In explorer...tools...folder options...View uncheck "Hide extensions for known file types" -9.3/ Double click run_fpdb.bat -9.4/ check the contents of pyfpdb\fpdb.exe.log, deal with any errors thrown - -9.5/ hopefully, fpdb will run -9.6/ Try out a few options, deal with any errors reported - -Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. - - -Step 11 pruning ---------------- - -11.1/ The generated folder is 100+MB and can be pruned by removing the following directories: - -pyfpdb/lib/glib-2.0 -pyfpdb/lib/pkgconfig -pyfpdb/share/aclocal -pyfpdb/share/doc -pyfpdb/share/glib-2.0 -pyfpdb/share/gtk-2.0 -pyfpdb/share/gtk-doc -pyfpdb/share/locale -pyfpdb/share/man - - -Step 12 rename folder ---------------------- - -If needed, rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. - - -Step 13 Compress to executable archive --------------------------------------- - -13.1/ Download and install 7zip 914 ... http://sourceforge.net/projects/sevenzip/files/7-Zip/9.14/7z914.exe/download -13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch -13.3/ Test the created exe file - -Step 14 If you need to build again for a different SSE level ------------------------------------------------------------- - -Go back to step 5 and run again. - +PY2EXE walkthrough for Python 2.7 & FPDB 0.2x +created on 27th Feb 2011 by Gimick + +This walkthrough is derived from comments in the py2exe script made by Ray and SqlCoder +Additional information, formatting, updating to Python 2.6 and Python 2.7 and sequencing added by Gimick +Content is available under the the GNU Affero General Public License version 3 + + + +Step 0 Get a fresh XP installation +---------------------------------- + +0.1/ Using XPhome or Pro 32bit + +0.2/ Ensure the CPU supports SSE2 instruction set or better. + + +Step 1, dependency install +-------------------------- + +1.1/ install the following in sequence (accept all default options) there should be no errors ! + +Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi +matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/matplotlib-1.0.1.win32-py2.7.exe/download +pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-2.22.0-1.win32-py2.7.exe +pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/pycairo-1.8.10.win32-py2.7.exe +pyGobject X 2.26 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/pygobject-2.26.0-1.win32-py2.7.exe +pywin 216 ... http://sourceforge.net/projects/pywin32/files/pywin32/Build216/pywin32-216.win32-py2.7.exe/download + +pypokereval 138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pokereval-138.win32-py2.7.exe/download +cdecimal 2.2 ... http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.2.win32-py2.7.msi +psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.1.win32-py2.6-pg8.4.3-release.exe + +(Note: stickpeople is the offical repository, not a community build) + +py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download + +1.2/ MySQL + +Install the following file: +mysql-python 1.2.3 ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3.win32-py2.7.exe/download + +1.3/ pytz fixup to work in an executable package + +pytz needs runtime access to timezone definition files. pytz is hard-coded to search in the directory from which the pytz .py modules are being run. +In a py2exe package, this directory is actually a library.zip container file, so windows cannot find the timezone definitions, and will crash the app. + +We need to make a one-line change to pytz to search in the current working directory (which is not a container), and not the application directory. +The py2exe script copies the timezone datafiles into the package folder pyfpdb/zoneinfo. + +Thanks to Jeff Peck gmail.com> on the py2exe mailing list for documenting this problem and solution. + +1.3.1/ Navigate to C:\Python27\Lib\site-packages\pytz +1.3.2/ Edit __init__.py +1.3.3/ At line 55 replace the following line(s): + + filename = os.path.join(os.path.dirname(__file__), + 'zoneinfo', *name_parts) + +with this line: + + filename = os.path.join(os.getcwd(), 'zoneinfo', *name_parts) + +1.3.4/ Save and exit + + +1.4/ Patch py2exe to stop popup runtime error message + +see http://www.py2exe.org/index.cgi/StderrLog for technical info. + +1.4.1/ + +dos> write C:\Python27\Lib\site-packages\py2exe\boot_common.py + +replace: + atexit.register(alert, 0, + "See the logfile '%s' for details" % fname, + "Errors occurred") +with: + #atexit.register(alert, 0, + # "See the logfile '%s' for details" % fname, + # "Errors occurred") + +1.4.2/ save and exit + + + +Step 2 Setup GTK +----------------- + +There are quite a few GTK packages needed, and rather than install them individually, I used the official AllinOne from the GTK project. + +2,1/ Create a new folder c:\GTK + +2.2/ Extract the following zip file into c:\GTK + +gtk+ allinone 2.22.1 ... http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.22/gtk+-bundle_2.22.1-20101227_win32.zip + +2.3/ If everything has worked, you should have c:\GTK\bin \etc \lib \src and so on created. + +2.4/ The /share/doc and /share/gtk-doc folders are huge, so can be emptied now (leave the /doc and /gtk-doc folders + in place, but delete the content) + + +Step 3 Set GTK into the PATH variable +------------------------------------- + +The path for GTK isn't set by default, so need to let the o/s know where the GTK stuff is. + +3.1/ Rightclick on mycomputer to show system properties +3.2/ select advanced/environment Variables +3.3/ in "system variables" NOT "user variables" do the following +3.3.1/ create a new item as name: GTK_BASEPATH value: c:\GTK +3.3.2/ edit the item "path", press home to get to the first character and insert the following text, (no quotes, including semicolon) %GTK_BASEPATH%\bin; + +3.4/ to check, open command prompt and do: + +dos>path ... system should respond with ... PATH=c:\GTK\bin;C:\WIN........ + +3.5/ Give it a spin to test (hopefully an application will start, if not, something has gone wrong) + +dos> gtk-demo + + +Step 4 Get the fpdb GIT tree +---------------------------- + +4.1/ Best to take a copy to work with; following steps will assume that the fpdb folder is on the Desktop +4.2/ Edit the script in packaging/windows/py2exe_setup.py to set the fpdbver variable for this release + + +5.3/ Install correct Numpy for this build +----------------------------------------- + +Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. + +For this reason, do not just run the installer. We will force a nosse version, to minimise problems on +older client PC's + +5.3.1/ download the package to the Desktop + +numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download + +5.3.3/ You are normally wanting to build a package which works on all CPU's, so install for noSSE as follows: + +dos> cd Desktop +dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse + +5.3.4/ At the end of the installation, click on "show details" to confirm the installation. + +"Target CPU handles SSE2" +"Target CPU handles SSE3" +"nosse install (arch value: nosse)" +"Install NO SSE" +Extract: numpy-1.5.1-nosse.exe... 100% +Execute: "C:\DOCUME~1\user\LOCALS~1\Temp\numpy-1.5.1-nosse.exe" +Completed + +Step 6 Run py2exe to generate fpdb.exe +-------------------------------------- + +6.0/ Set version number of build folder +dos> cd Desktop\fpdb\packaging\windows +dos> write py2exe_setup.py +change the value of fpdbver and save file + +6.1/ Run the script to create the fpdb.exe bundle + +dos> cd Desktop\fpdb\packaging\windows +dos> c:\python27\python.exe py2exe_setup.py py2exe + +wait a while, watch lots of copying and whatever. + +6.2/ You should next get prompted for the GTK folder. +Enter c:\GTK + +6.3/ If there are no errors reported, it has probably worked, we will test soon. + +Build notes: + +There is a warning about dll's not included "umath.pyd - c:\Python27\lib\site-packages\numpy\core\umath.pyd" + - reason for this is not understood at present. (Umath is apparently included in the built package). + + +Step 7 not currently used +------------------------- + +Has been deleted + + +Step 8 Drag out the completed bundle +------------------------------------ + +py2exe creates a new folder for the created software bundle, drag this out to the desktop for ease of working. + +8.1/ Drag Desktop\fpdb\packaging\windows\fpdb-n.nn.nnn to Desktop\ + + +Step 9 Initial run +------------------ + +9.1/ Open the Desktop\fpdb-n.nn.nnn folder +9.2/ In explorer...tools...folder options...View uncheck "Hide extensions for known file types" +9.3/ Double click run_fpdb.bat +9.4/ check the contents of pyfpdb\fpdb.exe.log, deal with any errors thrown + +9.5/ hopefully, fpdb will run +9.6/ Try out a few options, deal with any errors reported + +Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. + + +Step 11 deleted +--------------- + +Has been deleted + + +Step 12 rename folder +--------------------- + +If needed, rename the folder to something meaningful to the community. + +Step 13 Compress to executable archive +-------------------------------------- + +13.1/ Download and install 7zip 914 ... http://sourceforge.net/projects/sevenzip/files/7-Zip/9.14/7z914.exe/download +13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch +13.3/ Test the created exe file + + diff --git a/packaging/windows/py2exe_setup.py b/packaging/windows/py2exe_setup.py index 25a54c79..723218ac 100644 --- a/packaging/windows/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -24,32 +24,25 @@ Py2exe script for fpdb. ######################################################################## #TODO: -# get rid of all the uneeded libraries (e.g., pyQT) # think about an installer -# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate -# include the lib needed to handle png files in mucked - #HOW TO USE this script: # +#- edit the fpdbver variable in this script (this value will be used to create the distribution folder name) #- cd to the folder where this script is stored, usually ...packaging/windows #- Run the script with python "py2exe_setup.py py2exe" #- You will frequently get messages about missing .dll files.just assume other # person will have them? we have copyright issues including some dll's -#- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should +#- If it works, you'll have a new dir fpdb-version which should # contain 2 dirs; gfx and pyfpdb and run_fpdb.bat -#- [ This bit is now automated: -# Last, you must copy the etc/, lib/ and share/ folders from your -# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders, -# not just the contents) ] -#- You can (should) then prune the etc/, lib/ and share/ folders to -# remove components we don't need. (see output at end of program run) # See walkthrough in packaging directory for versions used +# Very useful guide here : http://www.no-ack.org/2010/09/complete-guide-to-py2exe-for-pygtk.html # steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree # Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb +fpdbver = '0.22' import os import sys @@ -61,6 +54,10 @@ except: print "A parameter is required, quitting now" quit() +if sys.argv[1] <> "py2exe": + print "Parameter 1 is not valid, quitting now" + quit() + from distutils.core import setup import py2exe import glob @@ -75,12 +72,15 @@ def isSystemDLL(pathname): return origIsSystemDLL(pathname) def test_and_remove(top): + #print "Attempting to delete:", top if os.path.exists(top): if os.path.isdir(top): remove_tree(top) else: print "Unexpected file '"+top+"' found. Exiting." exit() + else: + "oops folder not found" def remove_tree(top): # Delete everything reachable from the directory named in 'top', @@ -89,8 +89,8 @@ def remove_tree(top): # could delete all your disk files. # sc: Nicked this from somewhere, added the if statement to try # make it a bit safer - if top in ('build','dist',distdir) and os.path.basename(os.getcwd()) == 'windows': - #print "removing directory '"+top+"' ..." + if (top in ('build','dist') or top.startswith(distdir)) and os.path.basename(os.getcwd()) == 'windows': + print "removing directory '"+top+"' ..." for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) @@ -111,8 +111,6 @@ def copy_file(source,destination): shutil.copy( source, destination ) -fpdbver = '0.21' - distdir = r'fpdb-' + fpdbver rootdir = r'../../' #cwd is normally /packaging/windows pydir = rootdir+'pyfpdb/' @@ -172,9 +170,6 @@ setup( ] + matplotlib.get_py2exe_datafiles() ) -# ,(distdir, [rootdir+'run_fpdb.bat']) -# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) -# ] + print "*** py2exe build phase complete ***" # copy zone info and fpdb translation folders @@ -185,7 +180,7 @@ copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) copy_tree (gfxdir, os.path.join(distdir, 'gfx')) copy_file (rootdir+'run_fpdb.bat', distdir) -print "*** Renaming dist folder as distribution pyfpdb folder ***" +print "*** Renaming dist folder as pyfpdb folder ***" dest = os.path.join(distdir, 'pyfpdb') os.rename( 'dist', dest ) @@ -196,30 +191,34 @@ while not os.path.exists(gtk_dir): gtk_dir = sys.stdin.readline().rstrip() print "*** copying GTK runtime ***" -dest = os.path.join(distdir, 'pyfpdb') +dest = os.path.join(distdir, 'pyfpdb', ) copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) -print "*** All done! ***" +print "*** Activating MS-Windows GTK theme ***" +gtkrc = open(os.path.join(distdir, 'pyfpdb', 'etc', 'gtk-2.0', 'gtkrc'), 'w') +print >>gtkrc, 'gtk-theme-name = "MS-Windows"' +gtkrc.close() + +print "*** deleting temporary build folder ***" test_and_remove('build') -print distdir+" is in the pyfpdb dir" -print """ -The following dirs can probably removed to make the final package smaller: -pyfpdb/lib/glib-2.0 -pyfpdb/lib/gtk-2.0/include -pyfpdb/lib/pkgconfig -pyfpdb/share/aclocal -pyfpdb/share/doc -pyfpdb/share/glib-2.0 -pyfpdb/share/gtk-2.0 -pyfpdb/share/gtk-doc -pyfpdb/share/locale -pyfpdb/share/man -pyfpdb/share/themes/Default +print "*** deleting folders to shrink package size ***" +test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'glib-2.0')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'gtk-2.0','include')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'pkgconfig')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'aclocal')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'doc')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'glib-2.0')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-2.0')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-doc')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'locale')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'man')) -Use 7-zip to zip up the distribution and create a self extracting archive and that's it! -""" +print "***++++++++++++++++++++++++++++++++++++++++++++++" +print "All done!" +print "The distribution folder "+distdir+" is in the pyfpdb dir" +print "***++++++++++++++++++++++++++++++++++++++++++++++" From 26fc81c01302428fbf92ba1357f5d9fde09706e3 Mon Sep 17 00:00:00 2001 From: gimick Date: Fri, 11 Mar 2011 09:51:53 +0000 Subject: [PATCH 12/23] Revert "py2exe: add more build automation, activate MS-Windows GTK theme" filetype mangled This reverts commit 0dfe6b3d78cb6e220b3ecd546ef5abb0b0b3f169. --- .../windows/py2exeWalkthroughPython27.txt | 472 +++++++++--------- packaging/windows/py2exe_setup.py | 73 +-- 2 files changed, 278 insertions(+), 267 deletions(-) diff --git a/packaging/windows/py2exeWalkthroughPython27.txt b/packaging/windows/py2exeWalkthroughPython27.txt index af498584..c1fe722c 100644 --- a/packaging/windows/py2exeWalkthroughPython27.txt +++ b/packaging/windows/py2exeWalkthroughPython27.txt @@ -1,231 +1,241 @@ -PY2EXE walkthrough for Python 2.7 & FPDB 0.2x -created on 27th Feb 2011 by Gimick - -This walkthrough is derived from comments in the py2exe script made by Ray and SqlCoder -Additional information, formatting, updating to Python 2.6 and Python 2.7 and sequencing added by Gimick -Content is available under the the GNU Affero General Public License version 3 - - - -Step 0 Get a fresh XP installation ----------------------------------- - -0.1/ Using XPhome or Pro 32bit - -0.2/ Ensure the CPU supports SSE2 instruction set or better. - - -Step 1, dependency install --------------------------- - -1.1/ install the following in sequence (accept all default options) there should be no errors ! - -Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi -matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/matplotlib-1.0.1.win32-py2.7.exe/download -pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-2.22.0-1.win32-py2.7.exe -pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/pycairo-1.8.10.win32-py2.7.exe -pyGobject X 2.26 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/pygobject-2.26.0-1.win32-py2.7.exe -pywin 216 ... http://sourceforge.net/projects/pywin32/files/pywin32/Build216/pywin32-216.win32-py2.7.exe/download - -pypokereval 138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pokereval-138.win32-py2.7.exe/download -cdecimal 2.2 ... http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.2.win32-py2.7.msi -psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.1.win32-py2.6-pg8.4.3-release.exe - -(Note: stickpeople is the offical repository, not a community build) - -py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download - -1.2/ MySQL - -Install the following file: -mysql-python 1.2.3 ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3.win32-py2.7.exe/download - -1.3/ pytz fixup to work in an executable package - -pytz needs runtime access to timezone definition files. pytz is hard-coded to search in the directory from which the pytz .py modules are being run. -In a py2exe package, this directory is actually a library.zip container file, so windows cannot find the timezone definitions, and will crash the app. - -We need to make a one-line change to pytz to search in the current working directory (which is not a container), and not the application directory. -The py2exe script copies the timezone datafiles into the package folder pyfpdb/zoneinfo. - -Thanks to Jeff Peck gmail.com> on the py2exe mailing list for documenting this problem and solution. - -1.3.1/ Navigate to C:\Python27\Lib\site-packages\pytz -1.3.2/ Edit __init__.py -1.3.3/ At line 55 replace the following line(s): - - filename = os.path.join(os.path.dirname(__file__), - 'zoneinfo', *name_parts) - -with this line: - - filename = os.path.join(os.getcwd(), 'zoneinfo', *name_parts) - -1.3.4/ Save and exit - - -1.4/ Patch py2exe to stop popup runtime error message - -see http://www.py2exe.org/index.cgi/StderrLog for technical info. - -1.4.1/ - -dos> write C:\Python27\Lib\site-packages\py2exe\boot_common.py - -replace: - atexit.register(alert, 0, - "See the logfile '%s' for details" % fname, - "Errors occurred") -with: - #atexit.register(alert, 0, - # "See the logfile '%s' for details" % fname, - # "Errors occurred") - -1.4.2/ save and exit - - - -Step 2 Setup GTK ------------------ - -There are quite a few GTK packages needed, and rather than install them individually, I used the official AllinOne from the GTK project. - -2,1/ Create a new folder c:\GTK - -2.2/ Extract the following zip file into c:\GTK - -gtk+ allinone 2.22.1 ... http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.22/gtk+-bundle_2.22.1-20101227_win32.zip - -2.3/ If everything has worked, you should have c:\GTK\bin \etc \lib \src and so on created. - -2.4/ The /share/doc and /share/gtk-doc folders are huge, so can be emptied now (leave the /doc and /gtk-doc folders - in place, but delete the content) - - -Step 3 Set GTK into the PATH variable -------------------------------------- - -The path for GTK isn't set by default, so need to let the o/s know where the GTK stuff is. - -3.1/ Rightclick on mycomputer to show system properties -3.2/ select advanced/environment Variables -3.3/ in "system variables" NOT "user variables" do the following -3.3.1/ create a new item as name: GTK_BASEPATH value: c:\GTK -3.3.2/ edit the item "path", press home to get to the first character and insert the following text, (no quotes, including semicolon) %GTK_BASEPATH%\bin; - -3.4/ to check, open command prompt and do: - -dos>path ... system should respond with ... PATH=c:\GTK\bin;C:\WIN........ - -3.5/ Give it a spin to test (hopefully an application will start, if not, something has gone wrong) - -dos> gtk-demo - - -Step 4 Get the fpdb GIT tree ----------------------------- - -4.1/ Best to take a copy to work with; following steps will assume that the fpdb folder is on the Desktop -4.2/ Edit the script in packaging/windows/py2exe_setup.py to set the fpdbver variable for this release - - -5.3/ Install correct Numpy for this build ------------------------------------------ - -Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. - -For this reason, do not just run the installer. We will force a nosse version, to minimise problems on -older client PC's - -5.3.1/ download the package to the Desktop - -numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download - -5.3.3/ You are normally wanting to build a package which works on all CPU's, so install for noSSE as follows: - -dos> cd Desktop -dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse - -5.3.4/ At the end of the installation, click on "show details" to confirm the installation. - -"Target CPU handles SSE2" -"Target CPU handles SSE3" -"nosse install (arch value: nosse)" -"Install NO SSE" -Extract: numpy-1.5.1-nosse.exe... 100% -Execute: "C:\DOCUME~1\user\LOCALS~1\Temp\numpy-1.5.1-nosse.exe" -Completed - -Step 6 Run py2exe to generate fpdb.exe --------------------------------------- - -6.0/ Set version number of build folder -dos> cd Desktop\fpdb\packaging\windows -dos> write py2exe_setup.py -change the value of fpdbver and save file - -6.1/ Run the script to create the fpdb.exe bundle - -dos> cd Desktop\fpdb\packaging\windows -dos> c:\python27\python.exe py2exe_setup.py py2exe - -wait a while, watch lots of copying and whatever. - -6.2/ You should next get prompted for the GTK folder. -Enter c:\GTK - -6.3/ If there are no errors reported, it has probably worked, we will test soon. - -Build notes: - -There is a warning about dll's not included "umath.pyd - c:\Python27\lib\site-packages\numpy\core\umath.pyd" - - reason for this is not understood at present. (Umath is apparently included in the built package). - - -Step 7 not currently used -------------------------- - -Has been deleted - - -Step 8 Drag out the completed bundle ------------------------------------- - -py2exe creates a new folder for the created software bundle, drag this out to the desktop for ease of working. - -8.1/ Drag Desktop\fpdb\packaging\windows\fpdb-n.nn.nnn to Desktop\ - - -Step 9 Initial run ------------------- - -9.1/ Open the Desktop\fpdb-n.nn.nnn folder -9.2/ In explorer...tools...folder options...View uncheck "Hide extensions for known file types" -9.3/ Double click run_fpdb.bat -9.4/ check the contents of pyfpdb\fpdb.exe.log, deal with any errors thrown - -9.5/ hopefully, fpdb will run -9.6/ Try out a few options, deal with any errors reported - -Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. - - -Step 11 deleted ---------------- - -Has been deleted - - -Step 12 rename folder ---------------------- - -If needed, rename the folder to something meaningful to the community. - -Step 13 Compress to executable archive --------------------------------------- - -13.1/ Download and install 7zip 914 ... http://sourceforge.net/projects/sevenzip/files/7-Zip/9.14/7z914.exe/download -13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch -13.3/ Test the created exe file - - +PY2EXE walkthrough for Python 2.7 & FPDB 0.2x +created on 27th Feb 2011 by Gimick + +This walkthrough is derived from comments in the py2exe script made by Ray and SqlCoder +Additional information, formatting, updating to Python 2.6 and Python 2.7 and sequencing added by Gimick +Content is available under the the GNU Affero General Public License version 3 + + + +Step 0 Get a fresh XP installation +---------------------------------- + +0.1/ Using XPhome or Pro 32bit + +0.2/ Ensure the CPU supports SSE2 instruction set or better. + + +Step 1, dependency install +-------------------------- + +1.1/ install the following in sequence (accept all default options) there should be no errors ! + +Python 2.7 ... http://python.org/ftp/python/2.7/python-2.7.msi +matplotlib 1.0.1 ... http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.0.1/matplotlib-1.0.1.win32-py2.7.exe/download +pygtk 2.22 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-2.22.0-1.win32-py2.7.exe +pycairo 1.8.10 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/pycairo-1.8.10.win32-py2.7.exe +pyGobject X 2.26 ... http://ftp.gnome.org/pub/GNOME/binaries/win32/pygobject/2.26/pygobject-2.26.0-1.win32-py2.7.exe +pywin 216 ... http://sourceforge.net/projects/pywin32/files/pywin32/Build216/pywin32-216.win32-py2.7.exe/download + +pypokereval 138 ... http://sourceforge.net/projects/fpdb/files/fpdb/pokereval-138.win32-py2.7.exe/download +cdecimal 2.2 ... http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.2.win32-py2.7.msi +psycopg2 ... http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.2.1.win32-py2.6-pg8.4.3-release.exe + +(Note: stickpeople is the offical repository, not a community build) + +py2exe 0.6.9 ... http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download + +1.2/ MySQL + +Install the following file: +mysql-python 1.2.3 ... http://sourceforge.net/projects/fpdb/files/fpdb/MySQL-python-1.2.3.win32-py2.7.exe/download + +1.3/ pytz fixup to work in an executable package + +pytz needs runtime access to timezone definition files. pytz is hard-coded to search in the directory from which the pytz .py modules are being run. +In a py2exe package, this directory is actually a library.zip container file, so windows cannot find the timezone definitions, and will crash the app. + +We need to make a one-line change to pytz to search in the current working directory (which is not a container), and not the application directory. +The py2exe script copies the timezone datafiles into the package folder pyfpdb/zoneinfo. + +Thanks to Jeff Peck gmail.com> on the py2exe mailing list for documenting this problem and solution. + +1.3.1/ Navigate to C:\Python27\Lib\site-packages\pytz +1.3.2/ Edit __init__.py +1.3.3/ At line 55 replace the following line(s): + + filename = os.path.join(os.path.dirname(__file__), + 'zoneinfo', *name_parts) + +with this line: + + filename = os.path.join(os.getcwd(), 'zoneinfo', *name_parts) + +1.3.4/ Save and exit + + +1.4/ Patch py2exe to stop popup runtime error message + +see http://www.py2exe.org/index.cgi/StderrLog for technical info. + +1.4.1/ + +dos> write C:\Python27\Lib\site-packages\py2exe\boot_common.py + +replace: + atexit.register(alert, 0, + "See the logfile '%s' for details" % fname, + "Errors occurred") +with: + #atexit.register(alert, 0, + # "See the logfile '%s' for details" % fname, + # "Errors occurred") + +1.4.2/ save and exit + + + +Step 2 Setup GTK +----------------- + +There are quite a few GTK packages needed, and rather than install them individually, I used the official AllinOne from the GTK project. + +2,1/ Create a new folder c:\GTK + +2.2/ Extract the following zip file into c:\GTK + +gtk+ allinone 2.22.1 ... http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.22/gtk+-bundle_2.22.1-20101227_win32.zip + +2.3/ If everything has worked, you should have c:\GTK\bin \etc \lib \src and so on created. + +2.4/ The /share/doc and /share/gtk-doc folders are huge, so can be emptied now (leave the /doc and /gtk-doc folders + in place, but delete the content) + + +Step 3 Set GTK into the PATH variable +------------------------------------- + +The path for GTK isn't set by default, so need to let the o/s know where the GTK stuff is. + +3.1/ Rightclick on mycomputer to show system properties +3.2/ select advanced/environment Variables +3.3/ in "system variables" NOT "user variables" do the following +3.3.1/ create a new item as name: GTK_BASEPATH value: c:\GTK +3.3.2/ edit the item "path", press home to get to the first character and insert the following text, (no quotes, including semicolon) %GTK_BASEPATH%\bin; + +3.4/ to check, open command prompt and do: + +dos>path ... system should respond with ... PATH=c:\GTK\bin;C:\WIN........ + +3.5/ Give it a spin to test (hopefully an application will start, if not, something has gone wrong) + +dos> gtk-demo + + +Step 4 Get the fpdb GIT tree +---------------------------- + +4.1/ Best to take a copy to work with; following steps will assume that the fpdb folder is on the Desktop +4.2/ Edit the script in packaging/windows/py2exe_setup.py to set the fpdbver variable for this release + + +5.3/ Install correct Numpy for this build +----------------------------------------- + +Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. + +For this reason, do not just run the installer downloaded. We will force a nosse version, to minimise problems on +older client PC's + +5.3.1/ download the package to the Desktop + +numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download + +5.3.3/ If you are wanting to build a package which works on all CPU's, install noSSE as follows: + +dos> cd Desktop +dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse + +5.3.4/ At the end of the installation, click on "show details" to confirm the installation. + +"Target CPU handles SSE2" +"Target CPU handles SSE3" +"nosse install (arch value: nosse)" +"Install NO SSE" +Extract: numpy-1.5.1-nosse.exe... 100% +Execute: "C:\DOCUME~1\user\LOCALS~1\Temp\numpy-1.5.1-nosse.exe" +Completed + +Step 6 Run py2exe to generate fpdb.exe +-------------------------------------- + +6.1/ Run the script to create the fpdb.exe bundle + +dos> cd Desktop\fpdb\packaging\windows +dos> c:\python27\python.exe py2exe_setup.py py2exe + +wait a while, watch lots of copying and whatever. + +6.2/ You should next get prompted for the GTK folder. +c:\GTK + +6.3/ If there are no errors reported, it has probably worked, we will test soon. + +Build notes: + +There is a warning about dll's not included "umath.pyd - c:\Python27\lib\site-packages\numpy\core\umath.pyd" + - reason for this is not understood at present. (Umath is apparently included in the built package). + + +Step 7 not currently used +------------------------- + +Has been deleted + + +Step 8 Drag out the completed bundle +------------------------------------ + +py2exe creates a new folder for the created software bundle, drag this out to the desktop for ease of working. + +8.1/ Drag Desktop\fpdb\packaging\windows\fpdb-n.nn.nnn to Desktop\ + + +Step 9 Initial run +------------------ + +9.1/ Open the Desktop\fpdb-n.nn.nnn folder +9.2/ In explorer...tools...folder options...View uncheck "Hide extensions for known file types" +9.3/ Double click run_fpdb.bat +9.4/ check the contents of pyfpdb\fpdb.exe.log, deal with any errors thrown + +9.5/ hopefully, fpdb will run +9.6/ Try out a few options, deal with any errors reported + +Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. + + +Step 11 pruning +--------------- + +11.1/ The generated folder is 100+MB and can be pruned by removing the following directories: + +pyfpdb/lib/glib-2.0 +pyfpdb/lib/pkgconfig +pyfpdb/share/aclocal +pyfpdb/share/doc +pyfpdb/share/glib-2.0 +pyfpdb/share/gtk-2.0 +pyfpdb/share/gtk-doc +pyfpdb/share/locale +pyfpdb/share/man + + +Step 12 rename folder +--------------------- + +If needed, rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. + + +Step 13 Compress to executable archive +-------------------------------------- + +13.1/ Download and install 7zip 914 ... http://sourceforge.net/projects/sevenzip/files/7-Zip/9.14/7z914.exe/download +13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch +13.3/ Test the created exe file + +Step 14 If you need to build again for a different SSE level +------------------------------------------------------------ + +Go back to step 5 and run again. + diff --git a/packaging/windows/py2exe_setup.py b/packaging/windows/py2exe_setup.py index 723218ac..25a54c79 100644 --- a/packaging/windows/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -24,25 +24,32 @@ Py2exe script for fpdb. ######################################################################## #TODO: +# get rid of all the uneeded libraries (e.g., pyQT) # think about an installer +# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate +# include the lib needed to handle png files in mucked + #HOW TO USE this script: # -#- edit the fpdbver variable in this script (this value will be used to create the distribution folder name) #- cd to the folder where this script is stored, usually ...packaging/windows #- Run the script with python "py2exe_setup.py py2exe" #- You will frequently get messages about missing .dll files.just assume other # person will have them? we have copyright issues including some dll's -#- If it works, you'll have a new dir fpdb-version which should +#- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should # contain 2 dirs; gfx and pyfpdb and run_fpdb.bat +#- [ This bit is now automated: +# Last, you must copy the etc/, lib/ and share/ folders from your +# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders, +# not just the contents) ] +#- You can (should) then prune the etc/, lib/ and share/ folders to +# remove components we don't need. (see output at end of program run) # See walkthrough in packaging directory for versions used -# Very useful guide here : http://www.no-ack.org/2010/09/complete-guide-to-py2exe-for-pygtk.html # steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree # Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb -fpdbver = '0.22' import os import sys @@ -54,10 +61,6 @@ except: print "A parameter is required, quitting now" quit() -if sys.argv[1] <> "py2exe": - print "Parameter 1 is not valid, quitting now" - quit() - from distutils.core import setup import py2exe import glob @@ -72,15 +75,12 @@ def isSystemDLL(pathname): return origIsSystemDLL(pathname) def test_and_remove(top): - #print "Attempting to delete:", top if os.path.exists(top): if os.path.isdir(top): remove_tree(top) else: print "Unexpected file '"+top+"' found. Exiting." exit() - else: - "oops folder not found" def remove_tree(top): # Delete everything reachable from the directory named in 'top', @@ -89,8 +89,8 @@ def remove_tree(top): # could delete all your disk files. # sc: Nicked this from somewhere, added the if statement to try # make it a bit safer - if (top in ('build','dist') or top.startswith(distdir)) and os.path.basename(os.getcwd()) == 'windows': - print "removing directory '"+top+"' ..." + if top in ('build','dist',distdir) and os.path.basename(os.getcwd()) == 'windows': + #print "removing directory '"+top+"' ..." for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) @@ -111,6 +111,8 @@ def copy_file(source,destination): shutil.copy( source, destination ) +fpdbver = '0.21' + distdir = r'fpdb-' + fpdbver rootdir = r'../../' #cwd is normally /packaging/windows pydir = rootdir+'pyfpdb/' @@ -170,6 +172,9 @@ setup( ] + matplotlib.get_py2exe_datafiles() ) +# ,(distdir, [rootdir+'run_fpdb.bat']) +# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) +# ] + print "*** py2exe build phase complete ***" # copy zone info and fpdb translation folders @@ -180,7 +185,7 @@ copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) copy_tree (gfxdir, os.path.join(distdir, 'gfx')) copy_file (rootdir+'run_fpdb.bat', distdir) -print "*** Renaming dist folder as pyfpdb folder ***" +print "*** Renaming dist folder as distribution pyfpdb folder ***" dest = os.path.join(distdir, 'pyfpdb') os.rename( 'dist', dest ) @@ -191,34 +196,30 @@ while not os.path.exists(gtk_dir): gtk_dir = sys.stdin.readline().rstrip() print "*** copying GTK runtime ***" -dest = os.path.join(distdir, 'pyfpdb', ) +dest = os.path.join(distdir, 'pyfpdb') copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) -print "*** Activating MS-Windows GTK theme ***" -gtkrc = open(os.path.join(distdir, 'pyfpdb', 'etc', 'gtk-2.0', 'gtkrc'), 'w') -print >>gtkrc, 'gtk-theme-name = "MS-Windows"' -gtkrc.close() - -print "*** deleting temporary build folder ***" +print "*** All done! ***" test_and_remove('build') +print distdir+" is in the pyfpdb dir" +print """ +The following dirs can probably removed to make the final package smaller: -print "*** deleting folders to shrink package size ***" -test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'glib-2.0')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'gtk-2.0','include')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'pkgconfig')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'aclocal')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'doc')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'glib-2.0')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-2.0')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-doc')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'locale')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'man')) +pyfpdb/lib/glib-2.0 +pyfpdb/lib/gtk-2.0/include +pyfpdb/lib/pkgconfig +pyfpdb/share/aclocal +pyfpdb/share/doc +pyfpdb/share/glib-2.0 +pyfpdb/share/gtk-2.0 +pyfpdb/share/gtk-doc +pyfpdb/share/locale +pyfpdb/share/man +pyfpdb/share/themes/Default -print "***++++++++++++++++++++++++++++++++++++++++++++++" -print "All done!" -print "The distribution folder "+distdir+" is in the pyfpdb dir" -print "***++++++++++++++++++++++++++++++++++++++++++++++" +Use 7-zip to zip up the distribution and create a self extracting archive and that's it! +""" From f16e33c398ee3d2a832743745176f17b7e7a4a42 Mon Sep 17 00:00:00 2001 From: gimick Date: Fri, 11 Mar 2011 17:28:21 +0000 Subject: [PATCH 13/23] py2exe: add more build automation, activate MS-Windows GTK theme - (2nd try) --- .../windows/py2exeWalkthroughPython27.txt | 32 +++----- packaging/windows/py2exe_setup.py | 73 +++++++++---------- 2 files changed, 47 insertions(+), 58 deletions(-) diff --git a/packaging/windows/py2exeWalkthroughPython27.txt b/packaging/windows/py2exeWalkthroughPython27.txt index c1fe722c..921b591c 100644 --- a/packaging/windows/py2exeWalkthroughPython27.txt +++ b/packaging/windows/py2exeWalkthroughPython27.txt @@ -134,14 +134,14 @@ Step 4 Get the fpdb GIT tree Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. -For this reason, do not just run the installer downloaded. We will force a nosse version, to minimise problems on +For this reason, do not just run the installer. We will force a nosse version, to minimise problems on older client PC's 5.3.1/ download the package to the Desktop numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download -5.3.3/ If you are wanting to build a package which works on all CPU's, install noSSE as follows: +5.3.3/ You are normally wanting to build a package which works on all CPU's, so install for noSSE as follows: dos> cd Desktop dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse @@ -159,6 +159,11 @@ Completed Step 6 Run py2exe to generate fpdb.exe -------------------------------------- +6.0/ Set version number of build folder +dos> cd Desktop\fpdb\packaging\windows +dos> write py2exe_setup.py +change the value of fpdbver and save file + 6.1/ Run the script to create the fpdb.exe bundle dos> cd Desktop\fpdb\packaging\windows @@ -167,7 +172,7 @@ dos> c:\python27\python.exe py2exe_setup.py py2exe wait a while, watch lots of copying and whatever. 6.2/ You should next get prompted for the GTK folder. -c:\GTK +Enter c:\GTK 6.3/ If there are no errors reported, it has probably worked, we will test soon. @@ -205,27 +210,16 @@ Step 9 Initial run Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. -Step 11 pruning +Step 11 deleted --------------- -11.1/ The generated folder is 100+MB and can be pruned by removing the following directories: - -pyfpdb/lib/glib-2.0 -pyfpdb/lib/pkgconfig -pyfpdb/share/aclocal -pyfpdb/share/doc -pyfpdb/share/glib-2.0 -pyfpdb/share/gtk-2.0 -pyfpdb/share/gtk-doc -pyfpdb/share/locale -pyfpdb/share/man +Has been deleted Step 12 rename folder --------------------- -If needed, rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. - +If needed, rename the folder to something meaningful to the community. Step 13 Compress to executable archive -------------------------------------- @@ -234,8 +228,4 @@ Step 13 Compress to executable archive 13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch 13.3/ Test the created exe file -Step 14 If you need to build again for a different SSE level ------------------------------------------------------------- - -Go back to step 5 and run again. diff --git a/packaging/windows/py2exe_setup.py b/packaging/windows/py2exe_setup.py index 25a54c79..723218ac 100644 --- a/packaging/windows/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -24,32 +24,25 @@ Py2exe script for fpdb. ######################################################################## #TODO: -# get rid of all the uneeded libraries (e.g., pyQT) # think about an installer -# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate -# include the lib needed to handle png files in mucked - #HOW TO USE this script: # +#- edit the fpdbver variable in this script (this value will be used to create the distribution folder name) #- cd to the folder where this script is stored, usually ...packaging/windows #- Run the script with python "py2exe_setup.py py2exe" #- You will frequently get messages about missing .dll files.just assume other # person will have them? we have copyright issues including some dll's -#- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should +#- If it works, you'll have a new dir fpdb-version which should # contain 2 dirs; gfx and pyfpdb and run_fpdb.bat -#- [ This bit is now automated: -# Last, you must copy the etc/, lib/ and share/ folders from your -# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders, -# not just the contents) ] -#- You can (should) then prune the etc/, lib/ and share/ folders to -# remove components we don't need. (see output at end of program run) # See walkthrough in packaging directory for versions used +# Very useful guide here : http://www.no-ack.org/2010/09/complete-guide-to-py2exe-for-pygtk.html # steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree # Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb +fpdbver = '0.22' import os import sys @@ -61,6 +54,10 @@ except: print "A parameter is required, quitting now" quit() +if sys.argv[1] <> "py2exe": + print "Parameter 1 is not valid, quitting now" + quit() + from distutils.core import setup import py2exe import glob @@ -75,12 +72,15 @@ def isSystemDLL(pathname): return origIsSystemDLL(pathname) def test_and_remove(top): + #print "Attempting to delete:", top if os.path.exists(top): if os.path.isdir(top): remove_tree(top) else: print "Unexpected file '"+top+"' found. Exiting." exit() + else: + "oops folder not found" def remove_tree(top): # Delete everything reachable from the directory named in 'top', @@ -89,8 +89,8 @@ def remove_tree(top): # could delete all your disk files. # sc: Nicked this from somewhere, added the if statement to try # make it a bit safer - if top in ('build','dist',distdir) and os.path.basename(os.getcwd()) == 'windows': - #print "removing directory '"+top+"' ..." + if (top in ('build','dist') or top.startswith(distdir)) and os.path.basename(os.getcwd()) == 'windows': + print "removing directory '"+top+"' ..." for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) @@ -111,8 +111,6 @@ def copy_file(source,destination): shutil.copy( source, destination ) -fpdbver = '0.21' - distdir = r'fpdb-' + fpdbver rootdir = r'../../' #cwd is normally /packaging/windows pydir = rootdir+'pyfpdb/' @@ -172,9 +170,6 @@ setup( ] + matplotlib.get_py2exe_datafiles() ) -# ,(distdir, [rootdir+'run_fpdb.bat']) -# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) -# ] + print "*** py2exe build phase complete ***" # copy zone info and fpdb translation folders @@ -185,7 +180,7 @@ copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) copy_tree (gfxdir, os.path.join(distdir, 'gfx')) copy_file (rootdir+'run_fpdb.bat', distdir) -print "*** Renaming dist folder as distribution pyfpdb folder ***" +print "*** Renaming dist folder as pyfpdb folder ***" dest = os.path.join(distdir, 'pyfpdb') os.rename( 'dist', dest ) @@ -196,30 +191,34 @@ while not os.path.exists(gtk_dir): gtk_dir = sys.stdin.readline().rstrip() print "*** copying GTK runtime ***" -dest = os.path.join(distdir, 'pyfpdb') +dest = os.path.join(distdir, 'pyfpdb', ) copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) -print "*** All done! ***" +print "*** Activating MS-Windows GTK theme ***" +gtkrc = open(os.path.join(distdir, 'pyfpdb', 'etc', 'gtk-2.0', 'gtkrc'), 'w') +print >>gtkrc, 'gtk-theme-name = "MS-Windows"' +gtkrc.close() + +print "*** deleting temporary build folder ***" test_and_remove('build') -print distdir+" is in the pyfpdb dir" -print """ -The following dirs can probably removed to make the final package smaller: -pyfpdb/lib/glib-2.0 -pyfpdb/lib/gtk-2.0/include -pyfpdb/lib/pkgconfig -pyfpdb/share/aclocal -pyfpdb/share/doc -pyfpdb/share/glib-2.0 -pyfpdb/share/gtk-2.0 -pyfpdb/share/gtk-doc -pyfpdb/share/locale -pyfpdb/share/man -pyfpdb/share/themes/Default +print "*** deleting folders to shrink package size ***" +test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'glib-2.0')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'gtk-2.0','include')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'pkgconfig')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'aclocal')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'doc')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'glib-2.0')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-2.0')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-doc')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'locale')) +test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'man')) -Use 7-zip to zip up the distribution and create a self extracting archive and that's it! -""" +print "***++++++++++++++++++++++++++++++++++++++++++++++" +print "All done!" +print "The distribution folder "+distdir+" is in the pyfpdb dir" +print "***++++++++++++++++++++++++++++++++++++++++++++++" From 8cab75bf2ac48dc559fa6dbad15299f0cbb422e5 Mon Sep 17 00:00:00 2001 From: gimick Date: Sat, 12 Mar 2011 20:46:29 +0000 Subject: [PATCH 14/23] Revert "py2exe: add more build automation, activate MS-Windows GTK theme - (merge conflict)" This reverts commit f16e33c398ee3d2a832743745176f17b7e7a4a42. --- .../windows/py2exeWalkthroughPython27.txt | 32 +++++--- packaging/windows/py2exe_setup.py | 73 ++++++++++--------- 2 files changed, 58 insertions(+), 47 deletions(-) diff --git a/packaging/windows/py2exeWalkthroughPython27.txt b/packaging/windows/py2exeWalkthroughPython27.txt index 921b591c..c1fe722c 100644 --- a/packaging/windows/py2exeWalkthroughPython27.txt +++ b/packaging/windows/py2exeWalkthroughPython27.txt @@ -134,14 +134,14 @@ Step 4 Get the fpdb GIT tree Numpy needs special handling, as by default it will install an optimised version for the SSE level of your CPU (SSE3, SSE2 or noSSE). This means that the completed package will not run on an older CPU. -For this reason, do not just run the installer. We will force a nosse version, to minimise problems on +For this reason, do not just run the installer downloaded. We will force a nosse version, to minimise problems on older client PC's 5.3.1/ download the package to the Desktop numpy 1.5.1 ... http://sourceforge.net/projects/numpy/files/NumPy/1.5.1/numpy-1.5.1-win32-superpack-python2.7.exe/download -5.3.3/ You are normally wanting to build a package which works on all CPU's, so install for noSSE as follows: +5.3.3/ If you are wanting to build a package which works on all CPU's, install noSSE as follows: dos> cd Desktop dos> numpy-1.5.1-win32-superpack-python2.7.exe /arch nosse @@ -159,11 +159,6 @@ Completed Step 6 Run py2exe to generate fpdb.exe -------------------------------------- -6.0/ Set version number of build folder -dos> cd Desktop\fpdb\packaging\windows -dos> write py2exe_setup.py -change the value of fpdbver and save file - 6.1/ Run the script to create the fpdb.exe bundle dos> cd Desktop\fpdb\packaging\windows @@ -172,7 +167,7 @@ dos> c:\python27\python.exe py2exe_setup.py py2exe wait a while, watch lots of copying and whatever. 6.2/ You should next get prompted for the GTK folder. -Enter c:\GTK +c:\GTK 6.3/ If there are no errors reported, it has probably worked, we will test soon. @@ -210,16 +205,27 @@ Step 9 Initial run Observe that the msvcp90.dll was provided by the python runtime package, so we don't have to install the separate package from Microsoft. End-users will, however need the dependency. -Step 11 deleted +Step 11 pruning --------------- -Has been deleted +11.1/ The generated folder is 100+MB and can be pruned by removing the following directories: + +pyfpdb/lib/glib-2.0 +pyfpdb/lib/pkgconfig +pyfpdb/share/aclocal +pyfpdb/share/doc +pyfpdb/share/glib-2.0 +pyfpdb/share/gtk-2.0 +pyfpdb/share/gtk-doc +pyfpdb/share/locale +pyfpdb/share/man Step 12 rename folder --------------------- -If needed, rename the folder to something meaningful to the community. +If needed, rename the folder to something meaningful to the community. If you have built for NoSSE, append anyCPU to the directory name. + Step 13 Compress to executable archive -------------------------------------- @@ -228,4 +234,8 @@ Step 13 Compress to executable archive 13.2/ Rightclick on fpdb executable folder, select 7zip Add to archive... select SFX archive option switch 13.3/ Test the created exe file +Step 14 If you need to build again for a different SSE level +------------------------------------------------------------ + +Go back to step 5 and run again. diff --git a/packaging/windows/py2exe_setup.py b/packaging/windows/py2exe_setup.py index 723218ac..25a54c79 100644 --- a/packaging/windows/py2exe_setup.py +++ b/packaging/windows/py2exe_setup.py @@ -24,25 +24,32 @@ Py2exe script for fpdb. ######################################################################## #TODO: +# get rid of all the uneeded libraries (e.g., pyQT) # think about an installer +# done: change GuiAutoImport so that it knows to start HUD_main.exe, when appropriate +# include the lib needed to handle png files in mucked + #HOW TO USE this script: # -#- edit the fpdbver variable in this script (this value will be used to create the distribution folder name) #- cd to the folder where this script is stored, usually ...packaging/windows #- Run the script with python "py2exe_setup.py py2exe" #- You will frequently get messages about missing .dll files.just assume other # person will have them? we have copyright issues including some dll's -#- If it works, you'll have a new dir fpdb-version which should +#- If it works, you'll have a new dir fpdb-YYYYMMDD-exe which should # contain 2 dirs; gfx and pyfpdb and run_fpdb.bat +#- [ This bit is now automated: +# Last, you must copy the etc/, lib/ and share/ folders from your +# gtk/bin/ (just /gtk/?) folder to the pyfpdb folder. (the whole folders, +# not just the contents) ] +#- You can (should) then prune the etc/, lib/ and share/ folders to +# remove components we don't need. (see output at end of program run) # See walkthrough in packaging directory for versions used -# Very useful guide here : http://www.no-ack.org/2010/09/complete-guide-to-py2exe-for-pygtk.html # steffeN: Doesnt seem necessary to gettext-ify this, but feel free to if you disagree # Gimick: restructure to allow script to run from packaging/windows directory, and not to write to source pyfpdb -fpdbver = '0.22' import os import sys @@ -54,10 +61,6 @@ except: print "A parameter is required, quitting now" quit() -if sys.argv[1] <> "py2exe": - print "Parameter 1 is not valid, quitting now" - quit() - from distutils.core import setup import py2exe import glob @@ -72,15 +75,12 @@ def isSystemDLL(pathname): return origIsSystemDLL(pathname) def test_and_remove(top): - #print "Attempting to delete:", top if os.path.exists(top): if os.path.isdir(top): remove_tree(top) else: print "Unexpected file '"+top+"' found. Exiting." exit() - else: - "oops folder not found" def remove_tree(top): # Delete everything reachable from the directory named in 'top', @@ -89,8 +89,8 @@ def remove_tree(top): # could delete all your disk files. # sc: Nicked this from somewhere, added the if statement to try # make it a bit safer - if (top in ('build','dist') or top.startswith(distdir)) and os.path.basename(os.getcwd()) == 'windows': - print "removing directory '"+top+"' ..." + if top in ('build','dist',distdir) and os.path.basename(os.getcwd()) == 'windows': + #print "removing directory '"+top+"' ..." for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) @@ -111,6 +111,8 @@ def copy_file(source,destination): shutil.copy( source, destination ) +fpdbver = '0.21' + distdir = r'fpdb-' + fpdbver rootdir = r'../../' #cwd is normally /packaging/windows pydir = rootdir+'pyfpdb/' @@ -170,6 +172,9 @@ setup( ] + matplotlib.get_py2exe_datafiles() ) +# ,(distdir, [rootdir+'run_fpdb.bat']) +# ,(distdir+r'\gfx', glob.glob(gfxdir+'*.*')) +# ] + print "*** py2exe build phase complete ***" # copy zone info and fpdb translation folders @@ -180,7 +185,7 @@ copy_tree (pydir+r'locale', os.path.join(r'dist', 'locale')) copy_tree (gfxdir, os.path.join(distdir, 'gfx')) copy_file (rootdir+'run_fpdb.bat', distdir) -print "*** Renaming dist folder as pyfpdb folder ***" +print "*** Renaming dist folder as distribution pyfpdb folder ***" dest = os.path.join(distdir, 'pyfpdb') os.rename( 'dist', dest ) @@ -191,34 +196,30 @@ while not os.path.exists(gtk_dir): gtk_dir = sys.stdin.readline().rstrip() print "*** copying GTK runtime ***" -dest = os.path.join(distdir, 'pyfpdb', ) +dest = os.path.join(distdir, 'pyfpdb') copy_file(os.path.join(gtk_dir, 'bin', 'libgdk-win32-2.0-0.dll'), dest ) copy_file(os.path.join(gtk_dir, 'bin', 'libgobject-2.0-0.dll'), dest) copy_tree(os.path.join(gtk_dir, 'etc'), os.path.join(dest, 'etc')) copy_tree(os.path.join(gtk_dir, 'lib'), os.path.join(dest, 'lib')) copy_tree(os.path.join(gtk_dir, 'share'), os.path.join(dest, 'share')) -print "*** Activating MS-Windows GTK theme ***" -gtkrc = open(os.path.join(distdir, 'pyfpdb', 'etc', 'gtk-2.0', 'gtkrc'), 'w') -print >>gtkrc, 'gtk-theme-name = "MS-Windows"' -gtkrc.close() - -print "*** deleting temporary build folder ***" +print "*** All done! ***" test_and_remove('build') +print distdir+" is in the pyfpdb dir" +print """ +The following dirs can probably removed to make the final package smaller: -print "*** deleting folders to shrink package size ***" -test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'glib-2.0')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'gtk-2.0','include')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'lib', 'pkgconfig')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'aclocal')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'doc')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'glib-2.0')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-2.0')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'gtk-doc')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'locale')) -test_and_remove(os.path.join(distdir, 'pyfpdb', 'share', 'man')) +pyfpdb/lib/glib-2.0 +pyfpdb/lib/gtk-2.0/include +pyfpdb/lib/pkgconfig +pyfpdb/share/aclocal +pyfpdb/share/doc +pyfpdb/share/glib-2.0 +pyfpdb/share/gtk-2.0 +pyfpdb/share/gtk-doc +pyfpdb/share/locale +pyfpdb/share/man +pyfpdb/share/themes/Default -print "***++++++++++++++++++++++++++++++++++++++++++++++" -print "All done!" -print "The distribution folder "+distdir+" is in the pyfpdb dir" -print "***++++++++++++++++++++++++++++++++++++++++++++++" +Use 7-zip to zip up the distribution and create a self extracting archive and that's it! +""" From 920aac6f7342274cc219d7b087239815e6c495cb Mon Sep 17 00:00:00 2001 From: tribumarchal Date: Sun, 6 Mar 2011 16:26:17 +0100 Subject: [PATCH 15/23] fix minor bug display GuiRingPlayerStats.py Signed-off-by: tribumarchal --- pyfpdb/Card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/Card.py b/pyfpdb/Card.py index fcabf379..1bcc8dd5 100755 --- a/pyfpdb/Card.py +++ b/pyfpdb/Card.py @@ -98,7 +98,7 @@ def twoStartCardString(card): if x == y: ret = s[x] + s[y] elif x > y: ret = s[x] + s[y] + 's' else: ret = s[y] + s[x] + 'o' - print "twoStartCardString(", card ,") = " + ret + #print "twoStartCardString(", card ,") = " + ret return ret def fourStartCards(value1, suit1, value2, suit2, value3, suit3, value4, suit4): From c66ec208c25d641c29ce75e0b47b5e7abb5abc63 Mon Sep 17 00:00:00 2001 From: tribumarchal Date: Wed, 9 Mar 2011 16:32:42 +0100 Subject: [PATCH 16/23] fix error only french winnamax poker Signed-off-by: tribumarchal --- pyfpdb/WinamaxToFpdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/WinamaxToFpdb.py b/pyfpdb/WinamaxToFpdb.py index 5b092b94..907b6fd8 100644 --- a/pyfpdb/WinamaxToFpdb.py +++ b/pyfpdb/WinamaxToFpdb.py @@ -86,7 +86,7 @@ class Winamax(HandHistoryConverter): (?PCashGame)? (?PTournament\s (?P.+)?\s - buyIn:\s(?P(?P[%(LS)s\d\,]+)?\s\+?\s(?P[%(LS)s\d\,]+)?\+?(?P[%(LS)s\d\.]+)?\s?(?P%(LEGAL_ISO)s)?|Gratuit|Ticket\suniquement)?\s + buyIn:\s(?P(?P[%(LS)s\d\,]+)?\s\+?\s(?P[%(LS)s\d\,]+)?\+?(?P[%(LS)s\d\.]+)?\s?(?P%(LEGAL_ISO)s)?|Freeroll|Gratuit|Ticket\suniquement)?\s (level:\s(?P\d+))? .*)? \s-\sHandId:\s\#(?P\d+)-(?P\d+)-(?P\d+).*\s # REB says: HID3 is the correct hand number @@ -247,7 +247,7 @@ class Winamax(HandHistoryConverter): if k in info.keys() and info[k]: info[k] = info[k].replace(',','.') - if info[key] == 'Freeroll': + if info[key] == 'Gratuit': hand.buyin = 0 hand.fee = 0 hand.buyinCurrency = "FREE" From 7e5573f1f5a6b961b0fccc03e4d8ed933435f645 Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 14 Mar 2011 14:12:32 +0800 Subject: [PATCH 17/23] Winamax: Minor fix to Pierres patch. --- pyfpdb/WinamaxToFpdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyfpdb/WinamaxToFpdb.py b/pyfpdb/WinamaxToFpdb.py index 907b6fd8..2ecaf4a5 100644 --- a/pyfpdb/WinamaxToFpdb.py +++ b/pyfpdb/WinamaxToFpdb.py @@ -247,7 +247,7 @@ class Winamax(HandHistoryConverter): if k in info.keys() and info[k]: info[k] = info[k].replace(',','.') - if info[key] == 'Gratuit': + if info[key] == 'Gratuit' or info[key] == 'Freeroll': hand.buyin = 0 hand.fee = 0 hand.buyinCurrency = "FREE" @@ -259,7 +259,7 @@ class Winamax(HandHistoryConverter): elif info[key].find("FPP")!=-1: hand.buyinCurrency="PSFP" else: - #FIXME: handle other currencies, FPP, play money + #FIXME: handle other currencies (are there other currencies?) raise FpdbParseError(_("failed to detect currency")) info['BIAMT'] = info['BIAMT'].strip(u'$€FPP') From f1ec6590157a1426654650875f0a722d07add24d Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 14 Mar 2011 16:05:59 +0800 Subject: [PATCH 18/23] WinamaxSummary: Allow satellites to parse --- pyfpdb/WinamaxSummary.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pyfpdb/WinamaxSummary.py b/pyfpdb/WinamaxSummary.py index cd71293a..e1d5b0ce 100644 --- a/pyfpdb/WinamaxSummary.py +++ b/pyfpdb/WinamaxSummary.py @@ -60,6 +60,7 @@ class WinamaxSummary(TourneySummary): re_Prizepool = re.compile(u"""
.+: (?P[0-9,]+)""") re_DateTime = re.compile("\[(?P[0-9]{4})\/(?P[0-9]{2})\/(?P[0-9]{2})[\- ]+(?P[0-9]+):(?P[0-9]+):(?P[0-9]+)") + re_Ticket = re.compile(u""" / Ticket (?P[0-9.]+)€""") codepage = ["utf-8"] @@ -103,13 +104,23 @@ class WinamaxSummary(TourneySummary): self.gametype['category'] = self.games[mg['GAME']][1] for m in self.re_Player.finditer(str(tl[0])): + winnings = 0 mg = m.groupdict() - #print mg - winnings = mg['WINNINGS'].strip(u'€').replace(u',','.') - winnings = int(100*Decimal(winnings)) rank = mg['RANK'] name = mg['PNAME'] - #print "DEBUG: %s: %s" %(name, winnings) + #print "DEUBG: mg: '%s'" % mg + is_satellite = self.re_Ticket.search(mg['WINNINGS']) + if is_satellite: + # Ticket + winnings = convert_to_decimal(is_satellite.groupdict()['VALUE']) + # For stallites, any ticket means 1st + if winnings > 0: + rank = 1 + else: + winnings = convert_to_decimal(mg['WINNINGS']) + + winnings = int(100*Decimal(winnings)) + #print "DEBUG: %s) %s: %s" %(rank, name, winnings) self.addPlayer(rank, name, winnings, self.currency, None, None, None) @@ -117,3 +128,11 @@ class WinamaxSummary(TourneySummary): mg = m.groupdict() #print mg self.tourNo = mg['TOURNO'] + +def convert_to_decimal(string): + dec = string.strip(u'€€\u20ac') + dec = dec.replace(u',','.') + dec = dec.replace(u' ','') + dec = Decimal(dec) + return dec + From 2ddc08263ac3d44759e64a17aff8f630f3cdce57 Mon Sep 17 00:00:00 2001 From: tribumarchal Date: Tue, 8 Mar 2011 17:22:21 +0100 Subject: [PATCH 19/23] translation ascii utf8 Signed-off-by: tribumarchal --- pyfpdb/ScriptFetchWinamaxResults.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyfpdb/ScriptFetchWinamaxResults.py b/pyfpdb/ScriptFetchWinamaxResults.py index f0d4abcc..13ca8a8c 100644 --- a/pyfpdb/ScriptFetchWinamaxResults.py +++ b/pyfpdb/ScriptFetchWinamaxResults.py @@ -16,6 +16,8 @@ #In the "official" distribution you can find the license in agpl-3.0.txt """A script for fetching Winamax tourney results""" +import L10n +_ = L10n.get_translation() import Configuration import Database From 180c4be9f6d20fd42ad9164bf56e5a9d3def02e4 Mon Sep 17 00:00:00 2001 From: tribumarchal Date: Sun, 6 Mar 2011 20:58:03 +0100 Subject: [PATCH 20/23] new and old cards Signed-off-by: tribumarchal --- pyfpdb/Cards02.png | Bin 0 -> 100834 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyfpdb/Cards02.png diff --git a/pyfpdb/Cards02.png b/pyfpdb/Cards02.png new file mode 100644 index 0000000000000000000000000000000000000000..63fa6fdf4d68dc14aaf96fa0b629ddf3bfd997fb GIT binary patch literal 100834 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℘;lOy$M*^Icx&i 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 z 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 literal 0 HcmV?d00001 From affc697b322b27a53fbb3f44cc519a9b8eea11e6 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 15 Mar 2011 14:42:43 +0800 Subject: [PATCH 21/23] Regression: Absolute HH from email. Not this file was been modified from the original email format to: - Remove the ####+ line - Rename the file into the manner expected. --- ... Limit $0.10(Real Money) Table 9330000.txt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 pyfpdb/regression-test-files/cash/Absolute/Flop/IHH20110221 Emailes - Hold'em No Limit $0.10(Real Money) Table 9330000.txt diff --git a/pyfpdb/regression-test-files/cash/Absolute/Flop/IHH20110221 Emailes - Hold'em No Limit $0.10(Real Money) Table 9330000.txt b/pyfpdb/regression-test-files/cash/Absolute/Flop/IHH20110221 Emailes - Hold'em No Limit $0.10(Real Money) Table 9330000.txt new file mode 100644 index 00000000..95a89123 --- /dev/null +++ b/pyfpdb/regression-test-files/cash/Absolute/Flop/IHH20110221 Emailes - Hold'em No Limit $0.10(Real Money) Table 9330000.txt @@ -0,0 +1,54 @@ +Stage #C379012708: Holdem No Limit $0.10 Turbo - 2011-02-21 05:08:53.010 (ET) [ 2011-02-21 05:08:53 ] +Table: Holmbury Rd.59 (Real Money) Seat #6 is the dealer +Seat 6 - PLAYER6 ($11.19 in chips) +Seat 7 - PLAYER7 ($1.09 in chips) +Seat 9 - PLAYER9 ($11.95 in chips) +Seat 1 - PLAYER1 ($1.60 in chips) +Seat 3 - HERO ($11.56 in chips) +Seat 4 - PLAYER4 ($2.67 in chips) +Seat 5 - PLAYER5 ($8.34 in chips) +PLAYER7 - Posts small blind $0.05 +PLAYER9 - Posts big blind $0.10 +*** POCKET CARDS *** +Dealt to HERO [10c 4d] +PLAYER1 - Folds +HERO - Folds (Preselection) +PLAYER4 - Calls $0.10 (Preselection) +PLAYER5 - Raises $0.20 to $0.20 +PLAYER6 - Calls $0.20 +PLAYER7 - Folds +PLAYER9 - Calls $0.10 +PLAYER4 - Raises $0.20 to $0.30 (Preselection) +PLAYER5 - Calls $0.10 +PLAYER6 - Calls $0.10 +PLAYER9 - Calls $0.10 +*** FLOP *** [Kh 3s 5c] +PLAYER9 - Checks +PLAYER4 - Checks +PLAYER5 - Checks +PLAYER6 - Checks +*** TURN *** [Kh 3s 5c] [6c] +PLAYER9 - Checks +PLAYER4 - Checks +PLAYER5 - Bets $0.90 +PLAYER6 - Folds +PLAYER9 - Folds +PLAYER4 - Calls $0.90 +*** RIVER *** [Kh 3s 5c 6c] [5d] +PLAYER4 - All-In $1.47 +PLAYER5 - Calls $1.47 +*** SHOW DOWN *** +PLAYER4 - Shows [Qd 6d] (Two Pair, sixes and fives) +PLAYER5 - Shows [Js Jh] (Two Pair, jacks and fives) +PLAYER5 Collects $5.70 from main pot +*** SUMMARY *** +Total Pot($5.99) | Rake ($0.29) +Board [Kh 3s 5c 6c 5d] +Seat 1: PLAYER1 Folded on the POCKET CARDS +Seat 3: HERO Folded on the POCKET CARDS +Seat 4: PLAYER4 HI:lost with Two Pair, sixes and fives [Qd 6d - P:6d,B:6c,B:5d,B:5c,B:Kh] +Seat 5: PLAYER5 collected Total ($5.70) HI:($5.70) with Two Pair, jacks and fives [Js Jh - P:Js,P:Jh,B:5d,B:5c,B:Kh] +Seat 6: PLAYER6 (dealer) Folded on the TURN +Seat 7: PLAYER7 (small blind) Folded on the POCKET CARDS +Seat 9: PLAYER9 (big blind) Folded on the TURN + From 15bdbfbdf7999c36a9e4cece3b7639fd6166b245 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 15 Mar 2011 14:44:41 +0800 Subject: [PATCH 22/23] Absolute: Allow date variation to parse --- pyfpdb/AbsoluteToFpdb.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyfpdb/AbsoluteToFpdb.py b/pyfpdb/AbsoluteToFpdb.py index 299ff669..c31a52ab 100755 --- a/pyfpdb/AbsoluteToFpdb.py +++ b/pyfpdb/AbsoluteToFpdb.py @@ -55,8 +55,9 @@ class Absolute(HandHistoryConverter): (?PNo\ Limit|Pot\ Limit|Normal|)\s? (?P\$|\s€|) (?P[.,0-9]+)/?(?:\$|\s€|)(?P[.,0-9]+)? - \s+-\s+ - (?P\d\d\d\d-\d\d-\d\d\ \d\d:\d\d:\d\d)\s+ + \s+ + ((?P(Turbo))\s+)?-\s+ + ((?P\d\d\d\d-\d\d-\d\d\ \d\d:\d\d:\d\d)(\.\d+)?)\s+ (?: \( (?P[A-Z]+) \)\s+ )? .*? (Table:\ (?P.*?)\ \(Real\ Money\))? From 08ee109ec309d5270a04d7d82c606388e1623ea3 Mon Sep 17 00:00:00 2001 From: Worros Date: Thu, 17 Mar 2011 14:29:00 +0800 Subject: [PATCH 23/23] Stars: Allow ' in tablename Table 'Isildur's NLHE 50 IV' 2-max Seat #2 is the button --- pyfpdb/PokerStarsToFpdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 7b1add77..8807bf7b 100644 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -115,7 +115,7 @@ class PokerStars(HandHistoryConverter): re.MULTILINE|re.VERBOSE) re_HandInfo = re.compile(""" - ^Table\s\'(?P
[-\ \#a-zA-Z\d]+)\'\s + ^Table\s\'(?P
[-\ \#a-zA-Z\d\']+)\'\s ((?P\d+)-max\s)? (?P\(Play\sMoney\)\s)? (Seat\s\#(?P