Merge branch 'master' of git://git.assembla.com/fpdboz.git
This commit is contained in:
		
						commit
						cfdec93d36
					
				|  | @ -1,6 +1,7 @@ | |||
| #!/usr/bin/env python | ||||
| #    Copyright 2008, Carl Gherardi | ||||
| 
 | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| #    Copyright 2010, Matthew Boss | ||||
| #     | ||||
| #    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 | ||||
|  | @ -18,93 +19,286 @@ | |||
| 
 | ||||
| ######################################################################## | ||||
| 
 | ||||
| #    Standard Library modules | ||||
| import Configuration | ||||
| import traceback | ||||
| # This code is based heavily on EverleafToFpdb.py, by Carl Gherardi | ||||
| # | ||||
| # OUTSTANDING MATTERS | ||||
| # | ||||
| # -- No siteID assigned | ||||
| # -- No support for games other than NL hold 'em cash. Hand histories for other | ||||
| #    games required | ||||
| # -- No support for limit hold 'em yet, though this would be easy to add | ||||
| # -- No support for tournaments (see also the last item below) | ||||
| # -- Assumes that the currency of ring games is USD | ||||
| # -- Only works for 'gametype="2"'. What is 'gametype'? | ||||
| # -- Only accepts 'realmoney="true"' | ||||
| # -- A hand's time-stamp does not record seconds past the minute (a | ||||
| #    limitation of the history format) | ||||
| # -- No support for a bring-in or for antes (is the latter in fact unnecessary | ||||
| #    for hold 'em on Carbon?) | ||||
| # -- hand.maxseats can only be guessed at | ||||
| # -- The last hand in a history file will often be incomplete and is therefore | ||||
| #    rejected | ||||
| # -- Is behaviour currently correct when someone shows an uncalled hand? | ||||
| # -- Information may be lost when the hand ID is converted from the native form | ||||
| #    xxxxxxxx-yyy(y*) to xxxxxxxxyyy(y*) (in principle this should be stored as | ||||
| #    a string, but the database does not support this). Is there a possibility | ||||
| #    of collision between hand IDs that ought to be distinct? | ||||
| # -- Cannot parse tables that run it twice (nor is this likely ever to be | ||||
| #    possible) | ||||
| # -- Cannot parse hands in which someone is all in in one of the blinds. Until | ||||
| #    this is corrected tournaments will be unparseable | ||||
| 
 | ||||
| import sys | ||||
| import re | ||||
| import xml.dom.minidom | ||||
| from xml.dom.minidom import Node | ||||
| from HandHistoryConverter import HandHistoryConverter | ||||
| import logging | ||||
| from HandHistoryConverter import * | ||||
| from decimal import Decimal | ||||
| 
 | ||||
| # Carbon format looks like: | ||||
| class Carbon(HandHistoryConverter): | ||||
| 
 | ||||
| # 1) <description type="Holdem" stakes="No Limit ($0.25/$0.50)"/> | ||||
| # 2) <game id="14902583-5578" starttime="20081006145401" numholecards="2" gametype="2" realmoney="true" data="20081006|Niagara Falls (14902583)|14902583|14902583-5578|false"> | ||||
| # 3)  <players dealer="8"> | ||||
| #                <player seat="3" nickname="PlayerInSeat3" balance="$43.29" dealtin="true" /> | ||||
| #                ... | ||||
| # 4) <round id="BLINDS" sequence="1"> | ||||
| #                <event sequence="1" type="SMALL_BLIND" player="0" amount="0.25"/> | ||||
| #                <event sequence="2" type="BIG_BLIND" player="1" amount="0.50"/> | ||||
| # 5) <round id="PREFLOP" sequence="2"> | ||||
| #                <event sequence="3" type="CALL" player="2" amount="0.50"/> | ||||
| # 6) <round id="POSTFLOP" sequence="3"> | ||||
| #           <event sequence="16" type="BET" player="3" amount="1.00"/> | ||||
| #           .... | ||||
| #	    <cards type="COMMUNITY" cards="7d,Jd,Jh"/> | ||||
|     sitename = "Carbon" | ||||
|     filetype = "text" | ||||
|     codepage = "cp1252" | ||||
|     siteID   = 11 | ||||
| 
 | ||||
| # The full sequence for a NHLE cash game is: | ||||
| # BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME | ||||
| # This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_GAME | ||||
|     # Static regexes | ||||
|     re_SplitHands = re.compile(r'</game>\n+(?=<game)') | ||||
|     re_TailSplitHands = re.compile(r'(</game>)') | ||||
|     re_GameInfo = re.compile(r'<description type="(?P<GAME>[a-zA-Z ]+)" stakes="(?P<LIMIT>[a-zA-Z ]+) \(\$(?P<SB>[.0-9]+)/\$(?P<BB>[.0-9]+)\)"/>', re.MULTILINE) | ||||
|     re_HandInfo = re.compile(r'<game id="(?P<HID1>[0-9]+)-(?P<HID2>[0-9]+)" starttime="(?P<DATETIME>[0-9]+)" numholecards="2" gametype="2" realmoney="true" data="[0-9]+\|(?P<TABLE>[^\(]+)', re.MULTILINE) | ||||
|     re_Button = re.compile(r'<players dealer="(?P<BUTTON>[0-9]+)">') | ||||
|     re_PlayerInfo = re.compile(r'<player seat="(?P<SEAT>[0-9]+)" nickname="(?P<PNAME>.+)" balance="\$(?P<CASH>[.0-9]+)" dealtin="(?P<DEALTIN>(true|false))" />', re.MULTILINE) | ||||
|     re_Board = re.compile(r'<cards type="COMMUNITY" cards="(?P<CARDS>[^"]+)"', re.MULTILINE) | ||||
|     re_EndOfHand = re.compile(r'<round id="END_OF_GAME"', re.MULTILINE) | ||||
| 
 | ||||
|     # The following are also static regexes: there is no need to call | ||||
|     # compilePlayerRegexes (which does nothing), since players are identified | ||||
|     # not by name but by seat number | ||||
|     re_PostSB = re.compile(r'<event sequence="[0-9]+" type="(SMALL_BLIND|RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SB>[.0-9]+)"/>', re.MULTILINE) | ||||
|     re_PostBB = re.compile(r'<event sequence="[0-9]+" type="(BIG_BLIND|INITIAL_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<BB>[.0-9]+)"/>', re.MULTILINE) | ||||
|     re_PostBoth = re.compile(r'<event sequence="[0-9]+" type="(RETURN_BLIND)" player="(?P<PSEAT>[0-9])" amount="(?P<SBBB>[.0-9]+)"/>', re.MULTILINE) | ||||
|     #re_Antes = ??? | ||||
|     #re_BringIn = ??? | ||||
|     re_HeroCards = re.compile(r'<cards type="HOLE" cards="(?P<CARDS>.+)" player="(?P<PSEAT>[0-9])"', re.MULTILINE) | ||||
|     re_Action = re.compile(r'<event sequence="[0-9]+" type="(?P<ATYPE>FOLD|CHECK|CALL|BET|RAISE|ALL_IN|SIT_OUT)" player="(?P<PSEAT>[0-9])"( amount="(?P<BET>[.0-9]+)")?/>', re.MULTILINE) | ||||
|     re_ShowdownAction = re.compile(r'<cards type="SHOWN" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE) | ||||
|     re_CollectPot = re.compile(r'<winner amount="(?P<POT>[.0-9]+)" uncalled="(true|false)" potnumber="[0-9]+" player="(?P<PSEAT>[0-9])"', re.MULTILINE) | ||||
|     re_SitsOut = re.compile(r'<event sequence="[0-9]+" type="SIT_OUT" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE) | ||||
|     re_ShownCards = re.compile(r'<cards type="(SHOWN|MUCKED)" cards="(?P<CARDS>..,..)" player="(?P<PSEAT>[0-9])"/>', re.MULTILINE) | ||||
| 
 | ||||
| class CarbonPoker(HandHistoryConverter):  | ||||
| 	def __init__(self, config, filename): | ||||
| 		print "Initialising Carbon Poker converter class" | ||||
| 		HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init | ||||
| 		self.setFileType("xml") | ||||
|         self.siteId   = 4 # Needs to match id entry in Sites database | ||||
|     def compilePlayerRegexs(self, hand): | ||||
|         pass | ||||
| 
 | ||||
| 	def readSupportedGames(self):  | ||||
| 		pass | ||||
| 	def determineGameType(self): | ||||
| 		gametype = [] | ||||
| 		desc_node = self.doc.getElementsByTagName("description") | ||||
| 		#TODO: no examples of non ring type yet | ||||
| 		gametype = gametype + ["ring"] | ||||
| 		type = desc_node[0].getAttribute("type") | ||||
| 		if(type == "Holdem"): | ||||
| 			gametype = gametype + ["hold"] | ||||
| 		else: | ||||
| 			print "Carbon: Unknown gametype: '%s'" % (type) | ||||
|     def playerNameFromSeatNo(self, seatNo, hand): | ||||
|         # This special function is required because Carbon Poker records | ||||
|         # actions by seat number, not by the player's name | ||||
|         for p in hand.players: | ||||
|             if p[0] == int(seatNo): | ||||
|                 return p[1] | ||||
| 
 | ||||
| 		stakes = desc_node[0].getAttribute("stakes") | ||||
| 		#TODO: no examples of anything except nlhe | ||||
| 		m = re.match('(?P<LIMIT>No Limit)\s\(\$?(?P<SB>[.0-9]+)/\$?(?P<BB>[.0-9]+)\)', stakes) | ||||
|     def readSupportedGames(self): | ||||
|         return [["ring", "hold", "nl"], | ||||
|                 ["tour", "hold", "nl"]] | ||||
| 
 | ||||
| 		if(m.group('LIMIT') == "No Limit"): | ||||
| 			gametype = gametype + ["nl"] | ||||
|     def determineGameType(self, handText): | ||||
|         """return dict with keys/values: | ||||
|     'type'       in ('ring', 'tour') | ||||
|     'limitType'  in ('nl', 'cn', 'pl', 'cp', 'fl') | ||||
|     'base'       in ('hold', 'stud', 'draw') | ||||
|     'category'   in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi') | ||||
|     'hilo'       in ('h','l','s') | ||||
|     'smallBlind' int? | ||||
|     'bigBlind'   int? | ||||
|     'smallBet' | ||||
|     'bigBet' | ||||
|     'currency'  in ('USD', 'EUR', 'T$', <countrycode>) | ||||
| or None if we fail to get the info """ | ||||
| 
 | ||||
| 		gametype = gametype + [self.float2int(m.group('SB'))] | ||||
| 		gametype = gametype + [self.float2int(m.group('BB'))] | ||||
|         m = self.re_GameInfo.search(handText) | ||||
|         if not m: | ||||
|             # Information about the game type appears only at the beginning of | ||||
|             # a hand history file; hence it is not supplied with the second | ||||
|             # and subsequent hands. In these cases we use the value previously | ||||
|             # stored. | ||||
|             return self.info | ||||
|         self.info = {} | ||||
|         mg = m.groupdict() | ||||
| 
 | ||||
| 		return gametype | ||||
|         limits = { 'No Limit':'nl', 'Limit':'fl' } | ||||
|         games = {              # base, category | ||||
|                     'Holdem' : ('hold','holdem'), | ||||
|          'Holdem Tournament' : ('hold','holdem') } | ||||
| 
 | ||||
| 	def readPlayerStacks(self): | ||||
| 		pass | ||||
| 	def readBlinds(self): | ||||
| 		pass | ||||
| 	def readAction(self): | ||||
| 		pass | ||||
|         if 'LIMIT' in mg: | ||||
|             self.info['limitType'] = limits[mg['LIMIT']] | ||||
|         if 'GAME' in mg: | ||||
|             (self.info['base'], self.info['category']) = games[mg['GAME']] | ||||
|         if 'SB' in mg: | ||||
|             self.info['sb'] = mg['SB'] | ||||
|         if 'BB' in mg: | ||||
|             self.info['bb'] = mg['BB'] | ||||
|         if mg['GAME'] == 'Holdem Tournament': | ||||
|             self.info['type'] = 'tour' | ||||
|             self.info['currency'] = 'T$' | ||||
|         else: | ||||
|             self.info['type'] = 'ring' | ||||
|             self.info['currency'] = 'USD' | ||||
| 
 | ||||
| 	# Override read function as xml.minidom barfs on the Carbon layout | ||||
|         # This is pretty dodgy | ||||
| 	def readFile(self, filename): | ||||
| 		print "Carbon: Reading file: '%s'" %(filename) | ||||
| 		infile=open(filename, "rU") | ||||
| 		self.obs = infile.read() | ||||
| 		infile.close() | ||||
| 		self.obs = "<CarbonHHFile>\n" + self.obs + "</CarbonHHFile>" | ||||
| 		try: | ||||
| 			doc = xml.dom.minidom.parseString(self.obs) | ||||
| 			self.doc = doc | ||||
| 		except: | ||||
| 			traceback.print_exc(file=sys.stderr) | ||||
|         return self.info | ||||
| 
 | ||||
|     def readHandInfo(self, hand): | ||||
|         m = self.re_HandInfo.search(hand.handText) | ||||
|         if m is None: | ||||
|             logging.info("Didn't match re_HandInfo") | ||||
|             logging.info(hand.handText) | ||||
|             return None | ||||
|         logging.debug("HID %s-%s, Table %s" % (m.group('HID1'), | ||||
|                       m.group('HID2'), m.group('TABLE')[:-1])) | ||||
|         hand.handid = m.group('HID1') + m.group('HID2') | ||||
|         hand.tablename = m.group('TABLE')[:-1] | ||||
|         hand.maxseats = 2 # This value may be increased as necessary | ||||
|         hand.starttime = datetime.datetime.strptime(m.group('DATETIME')[:12], | ||||
|                                                     '%Y%m%d%H%M') | ||||
|         # Check that the hand is complete up to the awarding of the pot; if | ||||
|         # not, the hand is unparseable | ||||
|         if self.re_EndOfHand.search(hand.handText) is None: | ||||
|             raise FpdbParseError(hid=m.group('HID1') + "-" + m.group('HID2')) | ||||
| 
 | ||||
|     def readPlayerStacks(self, hand): | ||||
|         m = self.re_PlayerInfo.finditer(hand.handText) | ||||
|         for a in m: | ||||
|             seatno = int(a.group('SEAT')) | ||||
|             # It may be necessary to adjust 'hand.maxseats', which is an | ||||
|             # educated guess, starting with 2 (indicating a heads-up table) and | ||||
|             # adjusted upwards in steps to 6, then 9, then 10. An adjustment is | ||||
|             # made whenever a player is discovered whose seat number is | ||||
|             # currently above the maximum allowable for the table. | ||||
|             if seatno >= hand.maxseats: | ||||
|                 if seatno > 8: | ||||
|                     hand.maxseats = 10 | ||||
|                 elif seatno > 5: | ||||
|                     hand.maxseats = 9 | ||||
|                 else: | ||||
|                     hand.maxseats = 6 | ||||
|             if a.group('DEALTIN') == "true": | ||||
|                 hand.addPlayer(seatno, a.group('PNAME'), a.group('CASH')) | ||||
| 
 | ||||
|     def markStreets(self, hand): | ||||
|         #if hand.gametype['base'] == 'hold': | ||||
|         m = re.search(r'<round id="PREFLOP" sequence="[0-9]+">(?P<PREFLOP>.+(?=<round id="POSTFLOP")|.+)(<round id="POSTFLOP" sequence="[0-9]+">(?P<FLOP>.+(?=<round id="POSTTURN")|.+))?(<round id="POSTTURN" sequence="[0-9]+">(?P<TURN>.+(?=<round id="POSTRIVER")|.+))?(<round id="POSTRIVER" sequence="[0-9]+">(?P<RIVER>.+))?', hand.handText, re.DOTALL) | ||||
|         hand.addStreets(m) | ||||
| 
 | ||||
|     def readCommunityCards(self, hand, street): | ||||
|         m = self.re_Board.search(hand.streets[street]) | ||||
|         if street == 'FLOP': | ||||
|             hand.setCommunityCards(street, m.group('CARDS').split(',')) | ||||
|         elif street in ('TURN','RIVER'): | ||||
|             hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]]) | ||||
| 
 | ||||
|     def readAntes(self, hand): | ||||
|         pass # ??? | ||||
| 
 | ||||
|     def readBringIn(self, hand): | ||||
|         pass # ??? | ||||
| 
 | ||||
|     def readBlinds(self, hand): | ||||
|         try: | ||||
|             m = self.re_PostSB.search(hand.handText) | ||||
|             hand.addBlind(self.playerNameFromSeatNo(m.group('PSEAT'), hand), | ||||
|                           'small blind', m.group('SB')) | ||||
|         except: # no small blind | ||||
|             hand.addBlind(None, None, None) | ||||
|         for a in self.re_PostBB.finditer(hand.handText): | ||||
|             hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), hand), | ||||
|                           'big blind', a.group('BB')) | ||||
|         for a in self.re_PostBoth.finditer(hand.handText): | ||||
|             bb = Decimal(self.info['bb']) | ||||
|             amount = Decimal(a.group('SBBB')) | ||||
|             if amount < bb: | ||||
|                 hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), | ||||
|                               hand), 'small blind', a.group('SBBB')) | ||||
|             elif amount == bb: | ||||
|                 hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), | ||||
|                               hand), 'big blind', a.group('SBBB')) | ||||
|             else: | ||||
|                 hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), | ||||
|                               hand), 'both', a.group('SBBB')) | ||||
| 
 | ||||
|     def readButton(self, hand): | ||||
|         hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON')) | ||||
| 
 | ||||
|     def readHeroCards(self, hand): | ||||
|         m = self.re_HeroCards.search(hand.handText) | ||||
|         if m: | ||||
|             hand.hero = self.playerNameFromSeatNo(m.group('PSEAT'), hand) | ||||
|             cards = m.group('CARDS').split(',') | ||||
|             hand.addHoleCards('PREFLOP', hand.hero, closed=cards, shown=False, | ||||
|                               mucked=False, dealt=True) | ||||
| 
 | ||||
|     def readAction(self, hand, street): | ||||
|         logging.debug("readAction (%s)" % street) | ||||
|         m = self.re_Action.finditer(hand.streets[street]) | ||||
|         for action in m: | ||||
|             logging.debug("%s %s" % (action.group('ATYPE'), | ||||
|                                      action.groupdict())) | ||||
|             player = self.playerNameFromSeatNo(action.group('PSEAT'), hand) | ||||
|             if action.group('ATYPE') == 'RAISE': | ||||
|                 hand.addCallandRaise(street, player, action.group('BET')) | ||||
|             elif action.group('ATYPE') == 'CALL': | ||||
|                 hand.addCall(street, player, action.group('BET')) | ||||
|             elif action.group('ATYPE') == 'BET': | ||||
|                 hand.addBet(street, player, action.group('BET')) | ||||
|             elif action.group('ATYPE') in ('FOLD', 'SIT_OUT'): | ||||
|                 hand.addFold(street, player) | ||||
|             elif action.group('ATYPE') == 'CHECK': | ||||
|                 hand.addCheck(street, player) | ||||
|             elif action.group('ATYPE') == 'ALL_IN': | ||||
|                 hand.addAllIn(street, player, action.group('BET')) | ||||
|             else: | ||||
|                 logging.debug("Unimplemented readAction: %s %s" | ||||
|                               % (action.group('PSEAT'),action.group('ATYPE'),)) | ||||
| 
 | ||||
|     def readShowdownActions(self, hand): | ||||
|         for shows in self.re_ShowdownAction.finditer(hand.handText): | ||||
|             cards = shows.group('CARDS').split(',') | ||||
|             hand.addShownCards(cards, | ||||
|                                self.playerNameFromSeatNo(shows.group('PSEAT'), | ||||
|                                                          hand)) | ||||
| 
 | ||||
|     def readCollectPot(self, hand): | ||||
|         pots = [Decimal(0) for n in range(hand.maxseats)] | ||||
|         for m in self.re_CollectPot.finditer(hand.handText): | ||||
|             pots[int(m.group('PSEAT'))] += Decimal(m.group('POT')) | ||||
|         # Regarding the processing logic for "committed", see Pot.end() in | ||||
|         # Hand.py | ||||
|         committed = sorted([(v,k) for (k,v) in hand.pot.committed.items()]) | ||||
|         for p in range(hand.maxseats): | ||||
|             pname = self.playerNameFromSeatNo(p, hand) | ||||
|             if committed[-1][1] == pname: | ||||
|                 pots[p] -= committed[-1][0] - committed[-2][0] | ||||
|             if pots[p] > 0: | ||||
|                 hand.addCollectPot(player=pname, pot=pots[p]) | ||||
| 
 | ||||
|     def readShownCards(self, hand): | ||||
|         for m in self.re_ShownCards.finditer(hand.handText): | ||||
|             cards = m.group('CARDS').split(',') | ||||
|             hand.addShownCards(cards=cards, player=self.playerNameFromSeatNo(m.group('PSEAT'), hand)) | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
| 	c = Configuration.Config() | ||||
| 	e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml")  | ||||
| 	e.processFile() | ||||
| 	print str(e) | ||||
|     parser = OptionParser() | ||||
|     parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-") | ||||
|     parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-") | ||||
|     parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False) | ||||
|     parser.add_option("-q", "--quiet", action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO) | ||||
|     parser.add_option("-v", "--verbose", action="store_const", const=logging.INFO, dest="verbosity") | ||||
|     parser.add_option("--vv", action="store_const", const=logging.DEBUG, dest="verbosity") | ||||
| 
 | ||||
|     (options, args) = parser.parse_args() | ||||
| 
 | ||||
|     LOG_FILENAME = './logging.out' | ||||
|     logging.basicConfig(filename=LOG_FILENAME, level=options.verbosity) | ||||
| 
 | ||||
|     e = Carbon(in_path = options.ipath, | ||||
|                out_path = options.opath, | ||||
|                follow = options.follow, | ||||
|                autostart = True) | ||||
| 
 | ||||
|  |  | |||
|  | @ -56,36 +56,36 @@ def get_exec_path(): | |||
|     if hasattr(sys, "frozen"):  # compiled by py2exe | ||||
|         return os.path.dirname(sys.executable) | ||||
|     else: | ||||
|         pathname = os.path.dirname(sys.argv[0]) | ||||
|         return os.path.abspath(pathname) | ||||
|         return sys.path[0] | ||||
| 
 | ||||
| def get_config(file_name, fallback = True): | ||||
|     """Looks in cwd and in self.default_config_path for a config file.""" | ||||
|     config_path = os.path.join(get_exec_path(), file_name) | ||||
| #    print "config_path=", config_path | ||||
|     if os.path.exists(config_path):    # there is a file in the cwd | ||||
|         return config_path             # so we use it | ||||
|     else: # no file in the cwd, look where it should be in the first place | ||||
|         config_path = os.path.join(get_default_config_path(), file_name) | ||||
| #        print "config path 2=", config_path | ||||
|         if os.path.exists(config_path): | ||||
|     """Looks in exec dir and in self.default_config_path for a config file.""" | ||||
|     config_path = os.path.join(DIR_SELF, file_name) # look in exec dir | ||||
|     if os.path.exists(config_path) and os.path.isfile(config_path): | ||||
|         return config_path # there is a file in the exec dir so we use it | ||||
|     else: | ||||
|         config_path = os.path.join(DIR_CONFIG, file_name) # look in config dir | ||||
|         if os.path.exists(config_path) and os.path.isfile(config_path): | ||||
|             return config_path | ||||
| 
 | ||||
| #    No file found | ||||
|     if not fallback: | ||||
|         return False | ||||
| 
 | ||||
| #    OK, fall back to the .example file, should be in the start dir | ||||
|     if os.path.exists(file_name + ".example"): | ||||
| #    OK, fall back to the .example file, should be in the exec dir | ||||
|     if os.path.exists(os.path.join(DIR_SELF, file_name + ".example")): | ||||
|         try: | ||||
|             shutil.copyfile(file_name + ".example", file_name) | ||||
|             shutil.copyfile(os.path.join(DIR_SELF, file_name + ".example"), os.path.join(DIR_CONFIG, file_name)) | ||||
|             print "No %s found, using %s.example.\n" % (file_name, file_name) | ||||
|             print "A %s file has been created.  You will probably have to edit it." % file_name | ||||
|             sys.stderr.write("No %s found, using %s.example.\n" % (file_name, file_name) ) | ||||
|             print "A %s file has been created.  You will probably have to edit it." % os.path.join(DIR_CONFIG, file_name) | ||||
|             log.error("No %s found, using %s.example.\n" % (file_name, file_name) ) | ||||
|         except: | ||||
|             print "No %s found, cannot fall back. Exiting.\n" % file_name | ||||
|             sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name) | ||||
|             sys.exit() | ||||
|     else: | ||||
|         print "No %s found, cannot fall back. Exiting.\n" % file_name | ||||
|         sys.stderr.write("No %s found, cannot fall back. Exiting.\n" % file_name) | ||||
|         sys.exit() | ||||
|     return file_name | ||||
| 
 | ||||
| def get_logger(file_name, config = "config", fallback = False): | ||||
|  | @ -94,18 +94,26 @@ def get_logger(file_name, config = "config", fallback = False): | |||
|         try: | ||||
|             logging.config.fileConfig(conf) | ||||
|             log = logging.getLogger(config) | ||||
|             log.debug("%s logger initialised" % config) | ||||
|             return log | ||||
|         except: | ||||
|             pass | ||||
| 
 | ||||
|     log = logging.basicConfig() | ||||
|     log = logging.getLogger() | ||||
|     log.debug("config logger initialised") | ||||
|     log.error("basicConfig logger initialised") | ||||
|     return log | ||||
| 
 | ||||
| #    find a logging.conf file and set up logging | ||||
| log = get_logger("logging.conf") | ||||
| def check_dir(path, create = True): | ||||
|     """Check if a dir exists, optionally creates if not.""" | ||||
|     if os.path.exists(path): | ||||
|         if os.path.isdir(path): | ||||
|             return path | ||||
|         else: | ||||
|             return False | ||||
|     if create: | ||||
|         print "creating directory %s" % path | ||||
|     else: | ||||
|         return False | ||||
| 
 | ||||
| ######################################################################## | ||||
| # application wide consts | ||||
|  | @ -113,19 +121,31 @@ log = get_logger("logging.conf") | |||
| APPLICATION_NAME_SHORT = 'fpdb' | ||||
| APPLICATION_VERSION = 'xx.xx.xx' | ||||
| 
 | ||||
| DIR_SELF = os.path.dirname(get_exec_path()) | ||||
| #TODO: imo no good idea to place 'database' in parent dir | ||||
| DIR_DATABASES = os.path.join(os.path.dirname(DIR_SELF), 'database') | ||||
| DIR_SELF     = get_exec_path() | ||||
| DIR_CONFIG   = check_dir(get_default_config_path()) | ||||
| DIR_DATABASE = check_dir(os.path.join(DIR_CONFIG, 'database')) | ||||
| DIR_LOG      = check_dir(os.path.join(DIR_CONFIG, 'log')) | ||||
| 
 | ||||
| DATABASE_TYPE_POSTGRESQL = 'postgresql' | ||||
| DATABASE_TYPE_SQLITE = 'sqlite' | ||||
| DATABASE_TYPE_MYSQL = 'mysql' | ||||
| #TODO: should this be a tuple or a dict | ||||
| DATABASE_TYPES = ( | ||||
|         DATABASE_TYPE_POSTGRESQL, | ||||
|         DATABASE_TYPE_SQLITE, | ||||
|         DATABASE_TYPE_MYSQL, | ||||
|         ) | ||||
| 
 | ||||
| #    find a logging.conf file and set up logging | ||||
| log = get_logger("logging.conf", config = "config") | ||||
| log.debug("config logger initialised") | ||||
| 
 | ||||
| # and then log our consts | ||||
| log.info("DIR SELF = %s" % DIR_SELF) | ||||
| log.info("DIR CONFIG = %s" % DIR_CONFIG) | ||||
| log.info("DIR DATABASE = %s" % DIR_DATABASE) | ||||
| log.info("DIR LOG = %s" % DIR_LOG) | ||||
| NEWIMPORT = True | ||||
| LOCALE_ENCODING = locale.getdefaultlocale()[1] | ||||
| 
 | ||||
| ######################################################################## | ||||
|  | @ -408,11 +428,10 @@ class Config: | |||
|         if file is not None: # config file path passed in | ||||
|             file = os.path.expanduser(file) | ||||
|             if not os.path.exists(file): | ||||
|                 print "Configuration file %s not found.  Using defaults." % (file) | ||||
|                 sys.stderr.write("Configuration file %s not found.  Using defaults." % (file)) | ||||
|                 log.error("Specified configuration file %s not found.  Using defaults." % (file)) | ||||
|                 file = None | ||||
| 
 | ||||
|         if file is None: file = get_config("HUD_config.xml") | ||||
|         if file is None: file = get_config("HUD_config.xml", True) | ||||
| 
 | ||||
| #    Parse even if there was no real config file found and we are using the example | ||||
| #    If using the example, we'll edit it later | ||||
|  | @ -429,6 +448,8 @@ class Config: | |||
| 
 | ||||
|         self.doc = doc | ||||
|         self.file = file | ||||
|         self.dir = os.path.dirname(self.file) | ||||
|         self.dir_databases = os.path.join(self.dir, 'database') | ||||
|         self.supported_sites = {} | ||||
|         self.supported_games = {} | ||||
|         self.supported_databases = {}        # databaseName --> Database instance | ||||
|  |  | |||
|  | @ -38,11 +38,31 @@ from decimal import Decimal | |||
| import string | ||||
| import re | ||||
| import Queue | ||||
| import codecs | ||||
| import logging | ||||
| import math | ||||
| 
 | ||||
| 
 | ||||
| #    pyGTK modules | ||||
| 
 | ||||
| 
 | ||||
| #    Other library modules | ||||
| try: | ||||
|     import sqlalchemy.pool as pool | ||||
|     use_pool = True | ||||
| except ImportError: | ||||
|     logging.info("Not using sqlalchemy connection pool.") | ||||
|     use_pool = False | ||||
| 
 | ||||
| try: | ||||
|     from numpy import var | ||||
|     use_numpy = True | ||||
| except ImportError: | ||||
|     logging.info("Not using numpy to define variance in sqlite.") | ||||
|     use_numpy = False | ||||
| 
 | ||||
| 
 | ||||
| #    FreePokerTools modules | ||||
| import fpdb_db | ||||
| import Configuration | ||||
| import SQL | ||||
| import Card | ||||
|  | @ -50,7 +70,29 @@ import Tourney | |||
| import Charset | ||||
| from Exceptions import * | ||||
| 
 | ||||
| log = Configuration.get_logger("logging.conf") | ||||
| log = Configuration.get_logger("logging.conf", config = "db") | ||||
| log.debug("db logger initialized.") | ||||
| encoder = codecs.lookup('utf-8') | ||||
| 
 | ||||
| DB_VERSION = 119 | ||||
| 
 | ||||
| 
 | ||||
| # Variance created as sqlite has a bunch of undefined aggregate functions. | ||||
| 
 | ||||
| class VARIANCE: | ||||
|     def __init__(self): | ||||
|         self.store = [] | ||||
| 
 | ||||
|     def step(self, value): | ||||
|         self.store.append(value) | ||||
| 
 | ||||
|     def finalize(self): | ||||
|         return float(var(self.store)) | ||||
| 
 | ||||
| class sqlitemath: | ||||
|     def mod(self, a, b): | ||||
|         return a%b | ||||
| 
 | ||||
| 
 | ||||
| class Database: | ||||
| 
 | ||||
|  | @ -188,15 +230,14 @@ class Database: | |||
|         log.info("Creating Database instance, sql = %s" % sql) | ||||
|         self.config = c | ||||
|         self.__connected = False | ||||
|         self.fdb = fpdb_db.fpdb_db()   # sets self.fdb.db self.fdb.cursor and self.fdb.sql | ||||
|         self.do_connect(c) | ||||
|          | ||||
|         if self.backend == self.PGSQL: | ||||
|             from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE | ||||
|             #ISOLATION_LEVEL_AUTOCOMMIT     = 0 | ||||
|             #ISOLATION_LEVEL_READ_COMMITTED = 1  | ||||
|             #ISOLATION_LEVEL_SERIALIZABLE   = 2 | ||||
| 
 | ||||
|         self.settings = {} | ||||
|         self.settings['os'] = "linuxmac" if os.name != "nt" else "windows" | ||||
|         db_params = c.get_db_parameters() | ||||
|         self.import_options = c.get_import_parameters() | ||||
|         self.backend = db_params['db-backend'] | ||||
|         self.db_server = db_params['db-server'] | ||||
|         self.database = db_params['db-databaseName'] | ||||
|         self.host = db_params['db-host'] | ||||
| 
 | ||||
|         # where possible avoid creating new SQL instance by using the global one passed in | ||||
|         if sql is None: | ||||
|  | @ -204,6 +245,15 @@ class Database: | |||
|         else: | ||||
|             self.sql = sql | ||||
| 
 | ||||
|         # connect to db | ||||
|         self.do_connect(c) | ||||
|         print "connection =", self.connection | ||||
|         if self.backend == self.PGSQL: | ||||
|             from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE | ||||
|             #ISOLATION_LEVEL_AUTOCOMMIT     = 0 | ||||
|             #ISOLATION_LEVEL_READ_COMMITTED = 1  | ||||
|             #ISOLATION_LEVEL_SERIALIZABLE   = 2 | ||||
| 
 | ||||
|         if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion: | ||||
|             log.info("sqlite/:memory: - creating") | ||||
|             self.recreate_tables() | ||||
|  | @ -226,8 +276,6 @@ class Database: | |||
|         self.h_date_ndays_ago = 'd000000'  # date N days ago ('d' + YYMMDD) for hero | ||||
|         self.date_nhands_ago = {}          # dates N hands ago per player - not used yet | ||||
| 
 | ||||
|         self.cursor = self.fdb.cursor | ||||
| 
 | ||||
|         self.saveActions = False if self.import_options['saveActions'] == False else True | ||||
| 
 | ||||
|         self.connection.rollback()  # make sure any locks taken so far are released | ||||
|  | @ -238,14 +286,20 @@ class Database: | |||
|         self.hud_style = style | ||||
| 
 | ||||
|     def do_connect(self, c): | ||||
|         if c is None: | ||||
|             raise FpdbError('Configuration not defined') | ||||
| 
 | ||||
|         db = c.get_db_parameters() | ||||
|         try: | ||||
|             self.fdb.do_connect(c) | ||||
|             self.connect(backend=db['db-backend'], | ||||
|                          host=db['db-host'], | ||||
|                          database=db['db-databaseName'], | ||||
|                          user=db['db-user'], | ||||
|                          password=db['db-password']) | ||||
|         except: | ||||
|             # error during connect | ||||
|             self.__connected = False | ||||
|             raise | ||||
|         self.connection = self.fdb.db | ||||
|         self.wrongDbVersion = self.fdb.wrongDbVersion | ||||
| 
 | ||||
|         db_params = c.get_db_parameters() | ||||
|         self.import_options = c.get_import_parameters() | ||||
|  | @ -255,11 +309,137 @@ class Database: | |||
|         self.host = db_params['db-host'] | ||||
|         self.__connected = True | ||||
| 
 | ||||
|     def connect(self, backend=None, host=None, database=None, | ||||
|                 user=None, password=None): | ||||
|         """Connects a database with the given parameters""" | ||||
|         if backend is None: | ||||
|             raise FpdbError('Database backend not defined') | ||||
|         self.backend = backend | ||||
|         self.host = host | ||||
|         self.user = user | ||||
|         self.password = password | ||||
|         self.database = database | ||||
|         self.connection = None | ||||
|         self.cursor     = None | ||||
|          | ||||
|         if backend == Database.MYSQL_INNODB: | ||||
|             import MySQLdb | ||||
|             if use_pool: | ||||
|                 MySQLdb = pool.manage(MySQLdb, pool_size=5) | ||||
|             try: | ||||
|                 self.connection = MySQLdb.connect(host=host, user=user, passwd=password, db=database, use_unicode=True) | ||||
|             #TODO: Add port option | ||||
|             except MySQLdb.Error, ex: | ||||
|                 if ex.args[0] == 1045: | ||||
|                     raise FpdbMySQLAccessDenied(ex.args[0], ex.args[1]) | ||||
|                 elif ex.args[0] == 2002 or ex.args[0] == 2003: # 2002 is no unix socket, 2003 is no tcp socket | ||||
|                     raise FpdbMySQLNoDatabase(ex.args[0], ex.args[1]) | ||||
|                 else: | ||||
|                     print "*** WARNING UNKNOWN MYSQL ERROR", ex | ||||
|         elif backend == Database.PGSQL: | ||||
|             import psycopg2 | ||||
|             import psycopg2.extensions | ||||
|             if use_pool: | ||||
|                 psycopg2 = pool.manage(psycopg2, pool_size=5) | ||||
|             psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) | ||||
|             # If DB connection is made over TCP, then the variables | ||||
|             # host, user and password are required | ||||
|             # For local domain-socket connections, only DB name is | ||||
|             # needed, and everything else is in fact undefined and/or | ||||
|             # flat out wrong | ||||
|             # sqlcoder: This database only connect failed in my windows setup?? | ||||
|             # Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere? | ||||
|             connected = False | ||||
|             if self.host == "localhost" or self.host == "127.0.0.1": | ||||
|                 try: | ||||
|                     self.connection = psycopg2.connect(database = database) | ||||
|                     connected = True | ||||
|                 except: | ||||
|                     # direct connection failed so try user/pass/... version | ||||
|                     pass | ||||
|             if not connected: | ||||
|                 try: | ||||
|                     self.connection = psycopg2.connect(host = host, | ||||
|                                                user = user, | ||||
|                                                password = password, | ||||
|                                                database = database) | ||||
|                 except Exception, ex: | ||||
|                     if 'Connection refused' in ex.args[0]: | ||||
|                         # meaning eg. db not running | ||||
|                         raise FpdbPostgresqlNoDatabase(errmsg = ex.args[0]) | ||||
|                     elif 'password authentication' in ex.args[0]: | ||||
|                         raise FpdbPostgresqlAccessDenied(errmsg = ex.args[0]) | ||||
|                     else: | ||||
|                         msg = ex.args[0] | ||||
|                     print msg | ||||
|                     raise FpdbError(msg) | ||||
|         elif backend == Database.SQLITE: | ||||
|             logging.info("Connecting to SQLite: %(database)s" % {'database':database}) | ||||
|             import sqlite3 | ||||
|             if use_pool: | ||||
|                 sqlite3 = pool.manage(sqlite3, pool_size=1) | ||||
|             else: | ||||
|                 logging.warning("SQLite won't work well without 'sqlalchemy' installed.") | ||||
| 
 | ||||
|             if database != ":memory:": | ||||
|                 if not os.path.isdir(self.config.dir_databases): | ||||
|                     print "Creating directory: '%s'" % (self.config.dir_databases) | ||||
|                     logging.info("Creating directory: '%s'" % (self.config.dir_databases)) | ||||
|                     os.mkdir(self.config.dir_databases) | ||||
|                 database = os.path.join(self.config.dir_databases, database) | ||||
|             logging.info("  sqlite db: " + database) | ||||
|             self.connection = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES ) | ||||
|             sqlite3.register_converter("bool", lambda x: bool(int(x))) | ||||
|             sqlite3.register_adapter(bool, lambda x: "1" if x else "0") | ||||
|             self.connection.create_function("floor", 1, math.floor) | ||||
|             tmp = sqlitemath() | ||||
|             self.connection.create_function("mod", 2, tmp.mod) | ||||
|             if use_numpy: | ||||
|                 self.connection.create_aggregate("variance", 1, VARIANCE) | ||||
|             else: | ||||
|                 logging.warning("Some database functions will not work without NumPy support") | ||||
|         else: | ||||
|             raise FpdbError("unrecognised database backend:"+backend) | ||||
| 
 | ||||
|         self.cursor = self.connection.cursor() | ||||
|         self.cursor.execute(self.sql.query['set tx level']) | ||||
|         self.check_version(database=database, create=True) | ||||
| 
 | ||||
| 
 | ||||
|     def check_version(self, database, create): | ||||
|         self.wrongDbVersion = False | ||||
|         try: | ||||
|             self.cursor.execute("SELECT * FROM Settings") | ||||
|             settings = self.cursor.fetchone() | ||||
|             if settings[0] != DB_VERSION: | ||||
|                 logging.error("outdated or too new database version (%s) - please recreate tables" | ||||
|                               % (settings[0])) | ||||
|                 self.wrongDbVersion = True | ||||
|         except:# _mysql_exceptions.ProgrammingError: | ||||
|             if database !=  ":memory:": | ||||
|                 if create: | ||||
|                     print "Failed to read settings table - recreating tables" | ||||
|                     log.info("failed to read settings table - recreating tables") | ||||
|                     self.recreate_tables() | ||||
|                     self.check_version(database=database, create=False) | ||||
|                     if not self.wrongDbVersion: | ||||
|                         msg = "Edit your screen_name and hand history path in the supported_sites "\ | ||||
|                               +"section of the \nPreferences window (Main menu) before trying to import hands" | ||||
|                         print "\n%s" % msg | ||||
|                         log.warning(msg) | ||||
|                 else: | ||||
|                     print "Failed to read settings table - please recreate tables" | ||||
|                     log.info("failed to read settings table - please recreate tables") | ||||
|                     self.wrongDbVersion = True | ||||
|             else: | ||||
|                 self.wrongDbVersion = True | ||||
|     #end def connect | ||||
| 
 | ||||
|     def commit(self): | ||||
|         self.fdb.db.commit() | ||||
|         self.connection.commit() | ||||
| 
 | ||||
|     def rollback(self): | ||||
|         self.fdb.db.rollback() | ||||
|         self.connection.rollback() | ||||
| 
 | ||||
|     def connected(self): | ||||
|         return self.__connected | ||||
|  | @ -272,11 +452,18 @@ class Database: | |||
| 
 | ||||
|     def disconnect(self, due_to_error=False): | ||||
|         """Disconnects the DB (rolls back if param is true, otherwise commits""" | ||||
|         self.fdb.disconnect(due_to_error) | ||||
|         if due_to_error: | ||||
|             self.connection.rollback() | ||||
|         else: | ||||
|             self.connection.commit() | ||||
|         self.cursor.close() | ||||
|         self.connection.close() | ||||
|      | ||||
|     def reconnect(self, due_to_error=False): | ||||
|         """Reconnects the DB""" | ||||
|         self.fdb.reconnect(due_to_error=False) | ||||
|         #print "started reconnect" | ||||
|         self.disconnect(due_to_error) | ||||
|         self.connect(self.backend, self.host, self.database, self.user, self.password) | ||||
|      | ||||
|     def get_backend_name(self): | ||||
|         """Returns the name of the currently used backend""" | ||||
|  | @ -289,6 +476,9 @@ class Database: | |||
|         else: | ||||
|             raise FpdbError("invalid backend") | ||||
| 
 | ||||
|     def get_db_info(self): | ||||
|         return (self.host, self.database, self.user, self.password) | ||||
| 
 | ||||
|     def get_table_name(self, hand_id): | ||||
|         c = self.connection.cursor() | ||||
|         c.execute(self.sql.query['get_table_name'], (hand_id, )) | ||||
|  | @ -845,6 +1035,7 @@ class Database: | |||
|         self.create_tables() | ||||
|         self.createAllIndexes() | ||||
|         self.commit() | ||||
|         print "Finished recreating tables" | ||||
|         log.info("Finished recreating tables") | ||||
|     #end def recreate_tables | ||||
| 
 | ||||
|  | @ -1110,7 +1301,7 @@ class Database: | |||
|      | ||||
|     def fillDefaultData(self): | ||||
|         c = self.get_cursor()  | ||||
|         c.execute("INSERT INTO Settings (version) VALUES (118);") | ||||
|         c.execute("INSERT INTO Settings (version) VALUES (%s);" % (DB_VERSION)) | ||||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('Full Tilt Poker', 'USD')") | ||||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('PokerStars', 'USD')") | ||||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('Everleaf', 'USD')") | ||||
|  | @ -1121,6 +1312,7 @@ class Database: | |||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')") | ||||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')") | ||||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('Partouche', 'EUR')") | ||||
|         c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')") | ||||
|         if self.backend == self.SQLITE: | ||||
|             c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);") | ||||
|         elif self.backend == self.PGSQL: | ||||
|  | @ -1266,7 +1458,7 @@ class Database: | |||
|         try: | ||||
|             self.get_cursor().execute(self.sql.query['lockForInsert']) | ||||
|         except: | ||||
|             print "Error during fdb.lock_for_insert:", str(sys.exc_value) | ||||
|             print "Error during lock_for_insert:", str(sys.exc_value) | ||||
|     #end def lock_for_insert | ||||
| 
 | ||||
| ########################### | ||||
|  | @ -1285,6 +1477,7 @@ class Database: | |||
|                 p['tableName'],  | ||||
|                 p['gameTypeId'],  | ||||
|                 p['siteHandNo'],  | ||||
|                 0, # tourneyId: 0 means not a tourney hand | ||||
|                 p['handStart'],  | ||||
|                 datetime.today(), #importtime | ||||
|                 p['seats'], | ||||
|  |  | |||
|  | @ -27,8 +27,8 @@ import gobject | |||
| #import pokereval | ||||
| 
 | ||||
| import Configuration | ||||
| import fpdb_db | ||||
| import FpdbSQLQueries | ||||
| import Database | ||||
| import SQL | ||||
| import Charset | ||||
| 
 | ||||
| class Filters(threading.Thread): | ||||
|  | @ -800,10 +800,10 @@ def main(argv=None): | |||
|     config = Configuration.Config() | ||||
|     db = None | ||||
| 
 | ||||
|     db = fpdb_db.fpdb_db() | ||||
|     db = Database.Database() | ||||
|     db.do_connect(config) | ||||
| 
 | ||||
|     qdict = FpdbSQLQueries.FpdbSQLQueries(db.get_backend_name()) | ||||
|     qdict = SQL.SQL(db.get_backend_name()) | ||||
| 
 | ||||
|     i = Filters(db, config, qdict) | ||||
|     main_window = gtk.Window() | ||||
|  |  | |||
|  | @ -18,12 +18,10 @@ | |||
| #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||||
| ######################################################################## | ||||
| 
 | ||||
| import sys | ||||
| import logging | ||||
| from HandHistoryConverter import * | ||||
| 
 | ||||
| # Fulltilt HH Format converter | ||||
| # TODO: cat tourno and table to make table name for tournaments | ||||
| 
 | ||||
| class Fulltilt(HandHistoryConverter): | ||||
|      | ||||
|  | @ -67,8 +65,8 @@ class Fulltilt(HandHistoryConverter): | |||
|                                          (\s\((?P<TURBO>Turbo)\))?)|(?P<UNREADABLE_INFO>.+)) | ||||
|                                     ''', re.VERBOSE) | ||||
|     re_Button       = re.compile('^The button is in seat #(?P<BUTTON>\d+)', re.MULTILINE) | ||||
|     re_PlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE) | ||||
|     re_TourneyPlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.*) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE) | ||||
|     re_PlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$(?P<CASH>[,.0-9]+)\)$', re.MULTILINE) | ||||
|     re_TourneyPlayerInfo   = re.compile('Seat (?P<SEAT>[0-9]+): (?P<PNAME>.{3,15}) \(\$?(?P<CASH>[,.0-9]+)\)(, is sitting out)?$', re.MULTILINE) | ||||
|     re_Board        = re.compile(r"\[(?P<CARDS>.+)\]") | ||||
| 
 | ||||
|     #static regex for tourney purpose | ||||
|  | @ -191,7 +189,6 @@ class Fulltilt(HandHistoryConverter): | |||
|         if mg['TOURNO'] is None:  info['type'] = "ring" | ||||
|         else:                     info['type'] = "tour" | ||||
|         # NB: SB, BB must be interpreted as blinds or bets depending on limit type. | ||||
| #        if info['type'] == "tour": return None # importer is screwed on tournies, pass on those hands so we don't interrupt other autoimporting | ||||
|         return info | ||||
| 
 | ||||
|     def readHandInfo(self, hand): | ||||
|  | @ -258,15 +255,16 @@ class Fulltilt(HandHistoryConverter): | |||
| #TODO: Need some date functions to convert to different timezones (Date::Manip for perl rocked for this) | ||||
|         #hand.starttime = "%d/%02d/%02d %d:%02d:%02d ET" %(int(m.group('YEAR')), int(m.group('MON')), int(m.group('DAY')), | ||||
|                             ##int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC'))) | ||||
| #FIXME:        hand.buttonpos = int(m.group('BUTTON')) | ||||
| 
 | ||||
|     def readPlayerStacks(self, hand): | ||||
|         # Split hand text for FTP, as the regex matches the player names incorrectly | ||||
|         # in the summary section | ||||
|         pre, post = hand.handText.split('SUMMARY') | ||||
|         if hand.gametype['type'] == "ring" : | ||||
|             m = self.re_PlayerInfo.finditer(hand.handText) | ||||
|             m = self.re_PlayerInfo.finditer(pre) | ||||
|         else:   #if hand.gametype['type'] == "tour" | ||||
|             m = self.re_TourneyPlayerInfo.finditer(hand.handText) | ||||
|             m = self.re_TourneyPlayerInfo.finditer(pre) | ||||
| 
 | ||||
|         players = [] | ||||
|         for a in m: | ||||
|             hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) | ||||
| 
 | ||||
|  | @ -422,7 +420,6 @@ class Fulltilt(HandHistoryConverter): | |||
|             hand.mixed = self.mixes[m.groupdict()['MIXED']] | ||||
| 
 | ||||
|     def readSummaryInfo(self, summaryInfoList): | ||||
|         starttime = time.time() | ||||
|         self.status = True | ||||
| 
 | ||||
|         m = re.search("Tournament Summary", summaryInfoList[0]) | ||||
|  | @ -542,14 +539,14 @@ class Fulltilt(HandHistoryConverter): | |||
|                         tourney.buyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) | ||||
|                     else : | ||||
|                         if 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) != tourney.buyin: | ||||
|                             log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (touney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) ) | ||||
|                             log.error( "Conflict between buyins read in topline (%s) and in BuyIn field (%s)" % (tourney.buyin, 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN']))) ) | ||||
|                             tourney.subTourneyBuyin = 100*Decimal(re.sub(u',', u'', "%s" % mg['BUYIN'])) | ||||
|                 if mg['FEE'] is not None: | ||||
|                     if tourney.fee is None: | ||||
|                         tourney.fee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) | ||||
|                     else : | ||||
|                         if 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) != tourney.fee: | ||||
|                             log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (touney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) ) | ||||
|                             log.error( "Conflict between fees read in topline (%s) and in BuyIn field (%s)" % (tourney.fee, 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE']))) ) | ||||
|                             tourney.subTourneyFee = 100*Decimal(re.sub(u',', u'', "%s" % mg['FEE'])) | ||||
| 
 | ||||
|         if tourney.buyin is None: | ||||
|  |  | |||
|  | @ -300,7 +300,6 @@ if __name__== "__main__": | |||
|     (options, argv) = parser.parse_args() | ||||
| 
 | ||||
|     config = Configuration.Config() | ||||
| #    db = fpdb_db.fpdb_db() | ||||
| 
 | ||||
|     settings = {} | ||||
|     settings['minPrint'] = options.minPrint | ||||
|  |  | |||
|  | @ -27,7 +27,6 @@ from time import time, strftime | |||
| import Card | ||||
| import fpdb_import | ||||
| import Database | ||||
| import fpdb_db | ||||
| import Filters | ||||
| import Charset | ||||
| 
 | ||||
|  |  | |||
|  | @ -445,6 +445,43 @@ Left-Drag to Move" | |||
|                 <location seat="9" x="70" y="53">  </location> | ||||
|             </layout> | ||||
|         </site> | ||||
| 
 | ||||
| 
 | ||||
|         <site HH_path="C:/Program Files/Carbon Poker/HandHistory/YOUR SCREEN NAME HERE/" converter="CarbonToFpdb" decoder="everleaf_decode_table" enabled="True" screen_name="YOUR SCREEN NAME HERE" site_name="Carbon" site_path="C:/Program Files/Carbin/" supported_games="holdem" table_finder="Carbon Poker.exe"> | ||||
|             <layout fav_seat="0" height="547" max="8" width="794"> | ||||
|                 <location seat="1" x="640" y="64">  </location> | ||||
|                 <location seat="2" x="650" y="230"> </location> | ||||
|                 <location seat="3" x="650" y="385"> </location> | ||||
|                 <location seat="4" x="588" y="425"> </location> | ||||
|                 <location seat="5" x="92" y="425"> </location> | ||||
|                 <location seat="6" x="0" y="373"> </location> | ||||
|                 <location seat="7" x="0" y="223"> </location> | ||||
|                 <location seat="8" x="25" y="50">  </location> | ||||
|             </layout> | ||||
|             <layout fav_seat="0" height="547" max="6" width="794"> | ||||
|                 <location seat="1" x="640" y="58"> </location> | ||||
|                 <location seat="2" x="654" y="288"> </location> | ||||
|                 <location seat="3" x="615" y="424"> </location> | ||||
|                 <location seat="4" x="70" y="421"> </location> | ||||
|                 <location seat="5" x="0" y="280"> </location> | ||||
|                 <location seat="6" x="70" y="58"> </location> | ||||
|             </layout> | ||||
|             <layout fav_seat="0" height="547" max="2" width="794"> | ||||
|                 <location seat="1" x="651" y="288"> </location> | ||||
|                 <location seat="2" x="10" y="288"> </location> | ||||
|             </layout> | ||||
|             <layout fav_seat="0" height="547" max="9" width="794"> | ||||
|                 <location seat="1" x="634" y="38">  </location> | ||||
|                 <location seat="2" x="667" y="184"> </location> | ||||
|                 <location seat="3" x="667" y="321"> </location> | ||||
|                 <location seat="4" x="667" y="445"> </location> | ||||
|                 <location seat="5" x="337" y="459"> </location> | ||||
|                 <location seat="6" x="0" y="400"> </location> | ||||
|                 <location seat="7" x="0" y="322"> </location> | ||||
|                 <location seat="8" x="0" y="181">  </location> | ||||
|                 <location seat="9" x="70" y="53">  </location> | ||||
|             </layout> | ||||
|         </site> | ||||
|     </supported_sites> | ||||
| 
 | ||||
|     <supported_games> | ||||
|  | @ -585,11 +622,12 @@ Left-Drag to Move" | |||
|         <hhc site="PartyPoker" converter="PartyPokerToFpdb"/> | ||||
|         <hhc site="Betfair" converter="BetfairToFpdb"/> | ||||
|         <hhc site="Partouche" converter="PartoucheToFpdb"/> | ||||
|         <hhc site="Carbon" converter="CarbonToFpdb"/> | ||||
|     </hhcs> | ||||
| 
 | ||||
|     <supported_databases> | ||||
|         <database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database> | ||||
|         <!-- <database db_ip="localhost" db_name="fpdb" db_pass="fpdb" db_server="sqlite" db_user="fpdb"/> --> | ||||
|         <!-- <database db_name="fpdb" db_server="mysql" db_ip="localhost" db_user="fpdb" db_pass="YOUR MYSQL PASSWORD"></database> --> | ||||
|         <database db_ip="localhost" db_server="sqlite" db_name="fpdb.db3" db_user="fpdb" db_pass="fpdb"/> | ||||
|     </supported_databases> | ||||
| 
 | ||||
| </FreePokerToolsConfig> | ||||
|  |  | |||
|  | @ -36,9 +36,7 @@ import traceback | |||
| (options, argv) = Options.fpdb_options() | ||||
| 
 | ||||
| if not options.errorsToConsole: | ||||
|     print "Note: error output is being diverted to fpdb-error-log.txt and HUD-error.txt. Any major error will be reported there _only_." | ||||
|     errorFile = open('HUD-error.txt', 'w', 0) | ||||
|     sys.stderr = errorFile | ||||
|     print "Note: error output is being logged. Any major error will be reported there _only_." | ||||
| 
 | ||||
| import thread | ||||
| import time | ||||
|  | @ -52,6 +50,13 @@ import gobject | |||
| 
 | ||||
| #    FreePokerTools modules | ||||
| import Configuration | ||||
| 
 | ||||
| print "start logging" | ||||
| log = Configuration.get_logger("logging.conf", config = 'hud') | ||||
| log.debug("%s logger initialized." % "dud") | ||||
| print "logging started" | ||||
| 
 | ||||
| 
 | ||||
| import Database | ||||
| from HandHistoryConverter import getTableTitleRe | ||||
| #    get the correct module for the current os | ||||
|  | @ -72,6 +77,7 @@ class HUD_main(object): | |||
| 
 | ||||
|     def __init__(self, db_name = 'fpdb'): | ||||
|         self.db_name = db_name | ||||
|         self.log = log | ||||
|         self.config = Configuration.Config(file=options.config, dbname=options.dbname) | ||||
|         self.hud_dict = {} | ||||
|         self.hud_params = self.config.get_hud_ui_parameters() | ||||
|  | @ -91,6 +97,7 @@ class HUD_main(object): | |||
|         self.main_window.show_all() | ||||
| 
 | ||||
|     def destroy(self, *args):             # call back for terminating the main eventloop | ||||
|         self.log.info("Terminating normally.") | ||||
|         gtk.main_quit() | ||||
| 
 | ||||
|     def kill_hud(self, event, table): | ||||
|  | @ -198,6 +205,7 @@ class HUD_main(object): | |||
|             t0 = time.time() | ||||
|             t1 = t2 = t3 = t4 = t5 = t6 = t0 | ||||
|             new_hand_id = string.rstrip(new_hand_id) | ||||
|             self.log.debug("Received hand no %s" % new_hand_id) | ||||
|             if new_hand_id == "":           # blank line means quit | ||||
|                 self.destroy() | ||||
|                 break # this thread is not always killed immediately with gtk.main_quit() | ||||
|  | @ -207,9 +215,8 @@ class HUD_main(object): | |||
|             try: | ||||
|                 (table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \ | ||||
|                                 self.db_connection.get_table_info(new_hand_id) | ||||
|             except Exception, err: # TODO: we need to make this a much less generic Exception lulz | ||||
|                 print "db error: skipping %s" % new_hand_id | ||||
|                 sys.stderr.write("Database error: could not find hand %s.\n" % new_hand_id) | ||||
|             except Exception, err: | ||||
|                 self.log.error("db error: skipping %s" % new_hand_id) | ||||
|                 continue | ||||
|             t1 = time.time() | ||||
| 
 | ||||
|  | @ -267,7 +274,8 @@ class HUD_main(object): | |||
| #        If no client window is found on the screen, complain and continue | ||||
|                     if type == "tour": | ||||
|                         table_name = "%s %s" % (tour_number, tab_number) | ||||
|                     sys.stderr.write("HUD create: table name "+table_name+" not found, skipping.\n") | ||||
| #                    sys.stderr.write("HUD create: table name "+table_name+" not found, skipping.\n") | ||||
|                     self.log.error("HUD create: table name %s not found, skipping." % table_name) | ||||
|                 else: | ||||
|                     tablewindow.max = max | ||||
|                     tablewindow.site = site_name | ||||
|  | @ -284,8 +292,8 @@ class HUD_main(object): | |||
| 
 | ||||
| if __name__== "__main__": | ||||
| 
 | ||||
|     sys.stderr.write("HUD_main starting\n") | ||||
|     sys.stderr.write("Using db name = %s\n" % (options.dbname)) | ||||
|     log.info("HUD_main starting") | ||||
|     log.info("Using db name = %s" % (options.dbname)) | ||||
| 
 | ||||
| #    start the HUD_main object | ||||
|     hm = HUD_main(db_name = options.dbname) | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ class Hand(object): | |||
|     LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'} | ||||
|     SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''} | ||||
|     MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose'  : 'HOSE', 'ha': 'HA'} | ||||
|     SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 } | ||||
|     SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9, 'Partouche':10, 'Carbon':11 } | ||||
| 
 | ||||
| 
 | ||||
|     def __init__(self, sitename, gametype, handText, builtFrom = "HHC"): | ||||
|  | @ -205,7 +205,7 @@ dealt   whether they were seen in a 'dealt to' line | |||
|     def insert(self, db): | ||||
|         """ Function to insert Hand into database | ||||
| Should not commit, and do minimal selects. Callers may want to cache commits | ||||
| db: a connected fpdb_db object""" | ||||
| db: a connected Database object""" | ||||
| 
 | ||||
| 
 | ||||
|         self.stats.getStats(self) | ||||
|  | @ -289,6 +289,24 @@ If a player has None chips he won't be added.""" | |||
|             c = c.replace(k,v) | ||||
|         return c | ||||
| 
 | ||||
|     def addAllIn(self, street, player, amount): | ||||
|         """\ | ||||
| For sites (currently only Carbon Poker) which record "all in" as a special action, which can mean either "calls and is all in" or "raises all in". | ||||
| """ | ||||
|         self.checkPlayerExists(player) | ||||
|         amount = re.sub(u',', u'', amount) #some sites have commas | ||||
|         Ai = Decimal(amount) | ||||
|         Bp = self.lastBet[street] | ||||
|         Bc = reduce(operator.add, self.bets[street][player], 0) | ||||
|         C = Bp - Bc | ||||
|         if Ai <= C: | ||||
|             self.addCall(street, player, amount) | ||||
|         elif Bp == 0: | ||||
|             self.addBet(street, player, amount) | ||||
|         else: | ||||
|             Rb = Ai - C | ||||
|             self._addRaise(street, player, C, Rb, Ai) | ||||
| 
 | ||||
|     def addAnte(self, player, ante): | ||||
|         log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante)) | ||||
|         if player is not None: | ||||
|  | @ -396,7 +414,7 @@ Add a raise on [street] by [player] to [amountTo] | |||
|         Bc = reduce(operator.add, self.bets[street][player], 0) | ||||
|         Rt = Decimal(amountTo) | ||||
|         C = Bp - Bc | ||||
|         Rb = Rt - C | ||||
|         Rb = Rt - C - Bc | ||||
|         self._addRaise(street, player, C, Rb, Rt) | ||||
| 
 | ||||
|     def _addRaise(self, street, player, C, Rb, Rt): | ||||
|  |  | |||
|  | @ -26,29 +26,21 @@ from HandHistoryConverter import * | |||
| 
 | ||||
| # PartyPoker HH Format | ||||
| 
 | ||||
| class PartyPokerParseError(FpdbParseError): | ||||
|     "Usage: raise PartyPokerParseError(<msg>[, hh=<hh>][, hid=<hid>])" | ||||
| class FpdbParseError(FpdbParseError): | ||||
|     "Usage: raise FpdbParseError(<msg>[, hh=<hh>][, hid=<hid>])" | ||||
| 
 | ||||
|     def __init__(self, msg='', hh=None, hid=None): | ||||
|         if hh is not None: | ||||
|             msg += "\n\nHand history attached below:\n" + self.wrapHh(hh) | ||||
|         return super(PartyPokerParseError, self).__init__(msg, hid=hid) | ||||
|         return super(FpdbParseError, self).__init__(msg, hid=hid) | ||||
| 
 | ||||
|     def wrapHh(self, hh): | ||||
|         return ("%(DELIMETER)s\n%(HH)s\n%(DELIMETER)s") % \ | ||||
|                 {'DELIMETER': '#'*50, 'HH': hh} | ||||
| 
 | ||||
| class PartyPoker(HandHistoryConverter): | ||||
| 
 | ||||
| ############################################################ | ||||
| #    Class Variables | ||||
| 
 | ||||
|     sitename = "PartyPoker" | ||||
|     codepage = "cp1252" | ||||
|     siteId = 9 # TODO: automate; it's a class variable so shouldn't hit DB too often | ||||
|     filetype = "text" # "text" or "xml". I propose we subclass HHC to HHC_Text and HHC_XML. | ||||
| 
 | ||||
| 
 | ||||
|     siteId = 9  | ||||
|     filetype = "text"  | ||||
|     sym = {'USD': "\$", } | ||||
| 
 | ||||
|     # Static regexes | ||||
|  | @ -92,9 +84,9 @@ class PartyPoker(HandHistoryConverter): | |||
|             \((?P<PLAY>Real|Play)\s+Money\)\s+ # FIXME: check if play money is correct | ||||
|             Seat\s+(?P<BUTTON>\d+)\sis\sthe\sbutton | ||||
|             """, | ||||
|           re.MULTILINE|re.VERBOSE) | ||||
|           re.VERBOSE|re.MULTILINE) | ||||
| 
 | ||||
| #    re_TotalPlayers = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<MAXSEATS>\d+)", re.MULTILINE) | ||||
|     re_CountedSeats = re.compile("^Total\s+number\s+of\s+players\s*:\s*(?P<COUNTED_SEATS>\d+)", re.MULTILINE) | ||||
|     re_SplitHands   = re.compile('\x00+') | ||||
|     re_TailSplitHands   = re.compile('(\x00+)') | ||||
|     lineSplitter    = '\n' | ||||
|  | @ -131,16 +123,12 @@ class PartyPoker(HandHistoryConverter): | |||
|                 'CUR': hand.gametype['currency'] if hand.gametype['currency']!='T$' else ''} | ||||
|             for key in ('CUR_SYM', 'CUR'): | ||||
|                 subst[key] = re.escape(subst[key]) | ||||
|             log.debug("player_re: '%s'" % subst['PLYR']) | ||||
|             log.debug("CUR_SYM: '%s'" % subst['CUR_SYM']) | ||||
|             log.debug("CUR: '%s'" % subst['CUR']) | ||||
|             self.re_PostSB = re.compile( | ||||
|                 r"^%(PLYR)s posts small blind \[%(CUR_SYM)s(?P<SB>[.,0-9]+) ?%(CUR)s\]\." %  subst, | ||||
|                 re.MULTILINE) | ||||
|             self.re_PostBB = re.compile( | ||||
|                 r"^%(PLYR)s posts big blind \[%(CUR_SYM)s(?P<BB>[.,0-9]+) ?%(CUR)s\]\." %  subst, | ||||
|                 re.MULTILINE) | ||||
|             # NOTE: comma is used as a fraction part delimeter in re below | ||||
|             self.re_PostDead = re.compile( | ||||
|                 r"^%(PLYR)s posts big blind \+ dead \[(?P<BBNDEAD>[.,0-9]+) ?%(CUR_SYM)s\]\." %  subst, | ||||
|                 re.MULTILINE) | ||||
|  | @ -195,8 +183,6 @@ class PartyPoker(HandHistoryConverter): | |||
|         gametype dict is: | ||||
|         {'limitType': xxx, 'base': xxx, 'category': xxx}""" | ||||
| 
 | ||||
|         log.debug(PartyPokerParseError().wrapHh( handText )) | ||||
| 
 | ||||
|         info = {} | ||||
|         m = self._getGameType(handText) | ||||
|         if m is None: | ||||
|  | @ -213,22 +199,16 @@ class PartyPoker(HandHistoryConverter): | |||
| 
 | ||||
|         for expectedField in ['LIMIT', 'GAME']: | ||||
|             if mg[expectedField] is None: | ||||
|                 raise PartyPokerParseError( | ||||
|                     "Cannot fetch field '%s'" % expectedField, | ||||
|                     hh = handText) | ||||
|                 raise FpdbParseError( "Cannot fetch field '%s'" % expectedField) | ||||
|         try: | ||||
|             info['limitType'] = limits[mg['LIMIT'].strip()] | ||||
|         except: | ||||
|             raise PartyPokerParseError( | ||||
|                 "Unknown limit '%s'" % mg['LIMIT'], | ||||
|                 hh = handText) | ||||
|             raise FpdbParseError("Unknown limit '%s'" % mg['LIMIT']) | ||||
| 
 | ||||
|         try: | ||||
|             (info['base'], info['category']) = games[mg['GAME']] | ||||
|         except: | ||||
|             raise PartyPokerParseError( | ||||
|                 "Unknown game type '%s'" % mg['GAME'], | ||||
|                 hh = handText) | ||||
|             raise FpdbParseError("Unknown game type '%s'" % mg['GAME']) | ||||
| 
 | ||||
|         if 'TOURNO' in mg: | ||||
|             info['type'] = 'tour' | ||||
|  | @ -251,23 +231,21 @@ class PartyPoker(HandHistoryConverter): | |||
|         try: | ||||
|             info.update(self.re_Hid.search(hand.handText).groupdict()) | ||||
|         except: | ||||
|             raise PartyPokerParseError("Cannot read HID for current hand", hh=hand.handText) | ||||
|             raise FpdbParseError("Cannot read HID for current hand") | ||||
| 
 | ||||
|         try: | ||||
|             info.update(self.re_HandInfo.search(hand.handText,re.DOTALL).groupdict()) | ||||
|         except: | ||||
|             raise PartyPokerParseError("Cannot read Handinfo for current hand", | ||||
|             hh=hand.handText, hid = info['HID']) | ||||
|             raise FpdbParseError("Cannot read Handinfo for current hand", hid = info['HID']) | ||||
| 
 | ||||
|         try: | ||||
|             info.update(self._getGameType(hand.handText).groupdict()) | ||||
|         except: | ||||
|             raise PartyPokerParseError("Cannot read GameType for current hand", | ||||
|             hh=hand.handText, hid = info['HID']) | ||||
|             raise FpdbParseError("Cannot read GameType for current hand", hid = info['HID']) | ||||
| 
 | ||||
| 
 | ||||
| #        m = self.re_TotalPlayers.search(hand.handText) | ||||
| #        if m: info.update(m.groupdict()) | ||||
|         m = self.re_CountedSeats.search(hand.handText) | ||||
|         if m: info.update(m.groupdict()) | ||||
| 
 | ||||
| 
 | ||||
|         # FIXME: it's dirty hack | ||||
|  | @ -294,6 +272,7 @@ class PartyPoker(HandHistoryConverter): | |||
|             if key == 'DATETIME': | ||||
|                 #Saturday, July 25, 07:53:52 EDT 2009 | ||||
|                 #Thursday, July 30, 21:40:41 MSKS 2009 | ||||
|                 #Sunday, October 25, 13:39:07 MSK 2009 | ||||
|                 m2 = re.search("\w+, (?P<M>\w+) (?P<D>\d+), (?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+) (?P<TZ>[A-Z]+) (?P<Y>\d+)", info[key]) | ||||
|                 # we cant use '%B' due to locale problems | ||||
|                 months = ['January', 'February', 'March', 'April','May', 'June', | ||||
|  | @ -317,6 +296,10 @@ class PartyPoker(HandHistoryConverter): | |||
|                 hand.buttonpos = info[key] | ||||
|             if key == 'TOURNO': | ||||
|                 hand.tourNo = info[key] | ||||
|             if key == 'TABLE_ID_WRAPPER': | ||||
|                 if info[key] == '#': | ||||
|                     # FIXME: there is no such property in Hand class | ||||
|                     self.isSNG = True | ||||
|             if key == 'BUYIN': | ||||
|                 # FIXME: it's dirty hack T_T | ||||
|                 # code below assumes that tournament rake is equal to zero | ||||
|  | @ -328,7 +311,7 @@ class PartyPoker(HandHistoryConverter): | |||
|             if key == 'LEVEL': | ||||
|                 hand.level = info[key] | ||||
|             if key == 'PLAY' and info['PLAY'] != 'Real': | ||||
|                 # if realy there's no play money hh on party | ||||
|                 # if realy party doesn's save play money hh | ||||
|                 hand.gametype['currency'] = 'play' | ||||
| 
 | ||||
|     def readButton(self, hand): | ||||
|  | @ -413,8 +396,6 @@ class PartyPoker(HandHistoryConverter): | |||
|             blind = smartMin(hand.bb, playersMap[bigBlindSeat][1]) | ||||
|             hand.addBlind(playersMap[bigBlindSeat][0], 'big blind', blind) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     def readHeroCards(self, hand): | ||||
|         # we need to grab hero's cards | ||||
|         for street in ('PREFLOP',): | ||||
|  | @ -425,7 +406,6 @@ class PartyPoker(HandHistoryConverter): | |||
|                     newcards = renderCards(found.group('NEWCARDS')) | ||||
|                     hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) | ||||
| 
 | ||||
| 
 | ||||
|     def readAction(self, hand, street): | ||||
|         m = self.re_Action.finditer(hand.streets[street]) | ||||
|         for action in m: | ||||
|  | @ -460,10 +440,9 @@ class PartyPoker(HandHistoryConverter): | |||
|             elif actionType == 'checks': | ||||
|                 hand.addCheck( street, playerName ) | ||||
|             else: | ||||
|                 raise PartyPokerParseError( | ||||
|                 raise FpdbParseError( | ||||
|                     "Unimplemented readAction: '%s' '%s'" % (playerName,actionType,), | ||||
|                     hid = hand.hid, hh = hand.handText ) | ||||
| 
 | ||||
|                     hid = hand.hid, ) | ||||
| 
 | ||||
|     def readShowdownActions(self, hand): | ||||
|         # all action in readShownCards | ||||
|  | @ -482,6 +461,17 @@ class PartyPoker(HandHistoryConverter): | |||
| 
 | ||||
|                 hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=True, mucked=mucked) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def getTableTitleRe(type, table_name=None, tournament = None, table_number=None): | ||||
|         "Returns string to search in windows titles" | ||||
|         if type=="tour": | ||||
|             print 'party', 'getTableTitleRe', "%s.+Table\s#%s" % (table_name, table_number) | ||||
|             return "%s.+Table\s#%s" % (table_name, table_number) | ||||
|         else: | ||||
|             print 'party', 'getTableTitleRe', table_number | ||||
|             return table_name | ||||
| 
 | ||||
| 
 | ||||
| def ringBlinds(ringLimit): | ||||
|     "Returns blinds for current limit in cash games" | ||||
|     ringLimit = float(clearMoneyString(ringLimit)) | ||||
|  |  | |||
|  | @ -240,7 +240,6 @@ class PokerStars(HandHistoryConverter): | |||
|     def readPlayerStacks(self, hand): | ||||
|         log.debug("readPlayerStacks") | ||||
|         m = self.re_PlayerInfo.finditer(hand.handText) | ||||
|         players = [] | ||||
|         for a in m: | ||||
|             hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) | ||||
| 
 | ||||
|  |  | |||
|  | @ -58,6 +58,27 @@ class Sql: | |||
|         self.query['drop_table'] = """DROP TABLE IF EXISTS """    | ||||
| 
 | ||||
| 
 | ||||
|         ################################################################## | ||||
|         # Set transaction isolation level | ||||
|         ################################################################## | ||||
| 
 | ||||
|         if db_server == 'mysql' or db_server == 'postgresql': | ||||
|             self.query['set tx level'] = """SET SESSION TRANSACTION | ||||
|             ISOLATION LEVEL READ COMMITTED""" | ||||
|         elif db_server == 'sqlite': | ||||
|             self.query['set tx level'] = """ """ | ||||
| 
 | ||||
| 
 | ||||
|         ################################ | ||||
|         # Select basic info | ||||
|         ################################ | ||||
| 
 | ||||
|         self.query['getSiteId'] = """SELECT id from Sites where name = %s""" | ||||
| 
 | ||||
|         self.query['getGames'] = """SELECT DISTINCT category from Gametypes""" | ||||
|          | ||||
|         self.query['getLimits'] = """SELECT DISTINCT bigBlind from Gametypes ORDER by bigBlind DESC""" | ||||
| 
 | ||||
|         ################################ | ||||
|         # Create Settings | ||||
|         ################################ | ||||
|  | @ -214,6 +235,7 @@ class Sql: | |||
|                             id BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, PRIMARY KEY (id), | ||||
|                             tableName VARCHAR(22) NOT NULL, | ||||
|                             siteHandNo BIGINT NOT NULL, | ||||
|                             tourneyId INT UNSIGNED NOT NULL,  | ||||
|                             gametypeId SMALLINT UNSIGNED NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), | ||||
|                             handStart DATETIME NOT NULL, | ||||
|                             importTime DATETIME NOT NULL, | ||||
|  | @ -249,6 +271,7 @@ class Sql: | |||
|                             id BIGSERIAL, PRIMARY KEY (id), | ||||
|                             tableName VARCHAR(22) NOT NULL, | ||||
|                             siteHandNo BIGINT NOT NULL, | ||||
|                             tourneyId INT NOT NULL, | ||||
|                             gametypeId INT NOT NULL, FOREIGN KEY (gametypeId) REFERENCES Gametypes(id), | ||||
|                             handStart timestamp without time zone NOT NULL, | ||||
|                             importTime timestamp without time zone NOT NULL, | ||||
|  | @ -283,6 +306,7 @@ class Sql: | |||
|                             id INTEGER PRIMARY KEY, | ||||
|                             tableName TEXT(22) NOT NULL, | ||||
|                             siteHandNo INT NOT NULL, | ||||
|                             tourneyId INT NOT NULL, | ||||
|                             gametypeId INT NOT NULL, | ||||
|                             handStart REAL NOT NULL, | ||||
|                             importTime REAL NOT NULL, | ||||
|  | @ -3437,6 +3461,7 @@ class Sql: | |||
|                                             tablename, | ||||
|                                             gametypeid, | ||||
|                                             sitehandno, | ||||
|                                             tourneyId, | ||||
|                                             handstart, | ||||
|                                             importtime, | ||||
|                                             seats, | ||||
|  | @ -3467,7 +3492,7 @@ class Sql: | |||
|                                              VALUES | ||||
|                                               (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, | ||||
|                                                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, | ||||
|                                                %s, %s, %s, %s, %s, %s, %s, %s, %s)""" | ||||
|                                                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""" | ||||
| 
 | ||||
| 
 | ||||
|         self.query['store_hands_players'] = """INSERT INTO HandsPlayers ( | ||||
|  |  | |||
|  | @ -185,7 +185,7 @@ class Tourney(object): | |||
|     def old_insert_from_Hand(self, db): | ||||
|         """ Function to insert Hand into database | ||||
| Should not commit, and do minimal selects. Callers may want to cache commits | ||||
| db: a connected fpdb_db object""" | ||||
| db: a connected Database object""" | ||||
|         # TODO: | ||||
|         # Players - base playerid and siteid tuple | ||||
|         sqlids = db.getSqlPlayerIDs([p[1] for p in self.players], self.siteId) | ||||
|  |  | |||
|  | @ -112,7 +112,6 @@ import GuiGraphViewer | |||
| import GuiSessionViewer | ||||
| import SQL | ||||
| import Database | ||||
| import FpdbSQLQueries | ||||
| import Configuration | ||||
| import Exceptions | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,207 +16,6 @@ | |||
| #In the "official" distribution you can find the license in | ||||
| #agpl-3.0.txt in the docs folder of the package. | ||||
| 
 | ||||
| import os | ||||
| import re | ||||
| import sys | ||||
| import logging | ||||
| import math | ||||
| from time import time, strftime | ||||
| from Exceptions import * | ||||
| 
 | ||||
| try: | ||||
|     import sqlalchemy.pool as pool | ||||
|     use_pool = True | ||||
| except ImportError: | ||||
|     logging.info("Not using sqlalchemy connection pool.") | ||||
|     use_pool = False | ||||
| 
 | ||||
| try: | ||||
|     from numpy import var | ||||
|     use_numpy = True | ||||
| except ImportError: | ||||
|     logging.info("Not using numpy to define variance in sqlite.") | ||||
|     use_numpy = False | ||||
| 
 | ||||
| import FpdbSQLQueries | ||||
| import Configuration | ||||
| 
 | ||||
| # Variance created as sqlite has a bunch of undefined aggregate functions. | ||||
| 
 | ||||
| class VARIANCE: | ||||
|     def __init__(self): | ||||
|         self.store = [] | ||||
| 
 | ||||
|     def step(self, value): | ||||
|         self.store.append(value) | ||||
| 
 | ||||
|     def finalize(self): | ||||
|         return float(var(self.store)) | ||||
| 
 | ||||
| class sqlitemath: | ||||
|     def mod(self, a, b): | ||||
|         return a%b | ||||
| 
 | ||||
| class fpdb_db: | ||||
|     MYSQL_INNODB = 2 | ||||
|     PGSQL = 3 | ||||
|     SQLITE = 4 | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         """Simple constructor, doesnt really do anything""" | ||||
|         self.db             = None | ||||
|         self.cursor         = None | ||||
|         self.sql            = {} | ||||
|     #end def __init__ | ||||
| 
 | ||||
|     def do_connect(self, config=None): | ||||
|         """Connects a database using information in config""" | ||||
|         if config is None: | ||||
|             raise FpdbError('Configuration not defined') | ||||
| 
 | ||||
|         self.settings = {} | ||||
|         self.settings['os'] = "linuxmac" if os.name != "nt" else "windows" | ||||
| 
 | ||||
|         db = config.get_db_parameters() | ||||
|         self.connect(backend=db['db-backend'], | ||||
|                      host=db['db-host'], | ||||
|                      database=db['db-databaseName'], | ||||
|                      user=db['db-user'], | ||||
|                      password=db['db-password']) | ||||
|     #end def do_connect | ||||
| 
 | ||||
|     def connect(self, backend=None, host=None, database=None, | ||||
|                 user=None, password=None): | ||||
|         """Connects a database with the given parameters""" | ||||
|         if backend is None: | ||||
|             raise FpdbError('Database backend not defined') | ||||
|         self.backend = backend | ||||
|         self.host = host | ||||
|         self.user = user | ||||
|         self.password = password | ||||
|         self.database = database | ||||
|         if backend == fpdb_db.MYSQL_INNODB: | ||||
|             import MySQLdb | ||||
|             if use_pool: | ||||
|                 MySQLdb = pool.manage(MySQLdb, pool_size=5) | ||||
|             try: | ||||
|                 self.db = MySQLdb.connect(host=host, user=user, passwd=password, db=database, use_unicode=True) | ||||
|             #TODO: Add port option | ||||
|             except MySQLdb.Error, ex: | ||||
|                 if ex.args[0] == 1045: | ||||
|                     raise FpdbMySQLAccessDenied(ex.args[0], ex.args[1]) | ||||
|                 elif ex.args[0] == 2002 or ex.args[0] == 2003: # 2002 is no unix socket, 2003 is no tcp socket | ||||
|                     raise FpdbMySQLNoDatabase(ex.args[0], ex.args[1]) | ||||
|                 else: | ||||
|                     print "*** WARNING UNKNOWN MYSQL ERROR", ex | ||||
|         elif backend == fpdb_db.PGSQL: | ||||
|             import psycopg2 | ||||
|             import psycopg2.extensions | ||||
|             if use_pool: | ||||
|                 psycopg2 = pool.manage(psycopg2, pool_size=5) | ||||
|             psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) | ||||
|             # If DB connection is made over TCP, then the variables | ||||
|             # host, user and password are required | ||||
|             # For local domain-socket connections, only DB name is | ||||
|             # needed, and everything else is in fact undefined and/or | ||||
|             # flat out wrong | ||||
|             # sqlcoder: This database only connect failed in my windows setup?? | ||||
|             # Modifed it to try the 4 parameter style if the first connect fails - does this work everywhere? | ||||
|             connected = False | ||||
|             if self.host == "localhost" or self.host == "127.0.0.1": | ||||
|                 try: | ||||
|                     self.db = psycopg2.connect(database = database) | ||||
|                     connected = True | ||||
|                 except: | ||||
|                     # direct connection failed so try user/pass/... version | ||||
|                     pass | ||||
|             if not connected: | ||||
|                 try: | ||||
|                     self.db = psycopg2.connect(host = host, | ||||
|                                                user = user, | ||||
|                                                password = password, | ||||
|                                                database = database) | ||||
|                 except Exception, ex: | ||||
|                     if 'Connection refused' in ex.args[0]: | ||||
|                         # meaning eg. db not running | ||||
|                         raise FpdbPostgresqlNoDatabase(errmsg = ex.args[0]) | ||||
|                     elif 'password authentication' in ex.args[0]: | ||||
|                         raise FpdbPostgresqlAccessDenied(errmsg = ex.args[0]) | ||||
|                     else: | ||||
|                         msg = ex.args[0] | ||||
|                     print msg | ||||
|                     raise FpdbError(msg) | ||||
|         elif backend == fpdb_db.SQLITE: | ||||
|             logging.info("Connecting to SQLite:%(database)s" % {'database':database}) | ||||
|             import sqlite3 | ||||
|             if use_pool: | ||||
|                 sqlite3 = pool.manage(sqlite3, pool_size=1) | ||||
|             else: | ||||
|                 logging.warning("SQLite won't work well without 'sqlalchemy' installed.") | ||||
| 
 | ||||
|             if not os.path.isdir(Configuration.DIR_DATABASES) and not database ==  ":memory:": | ||||
|                 print "Creating directory: '%s'" % (Configuration.DIR_DATABASES) | ||||
|                 os.mkdir(Configuration.DIR_DATABASES) | ||||
|                 database = os.path.join(Configuration.DIR_DATABASES, database) | ||||
|             self.db = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES ) | ||||
|             sqlite3.register_converter("bool", lambda x: bool(int(x))) | ||||
|             sqlite3.register_adapter(bool, lambda x: "1" if x else "0") | ||||
|             self.db.create_function("floor", 1, math.floor) | ||||
|             tmp = sqlitemath() | ||||
|             self.db.create_function("mod", 2, tmp.mod) | ||||
|             if use_numpy: | ||||
|                 self.db.create_aggregate("variance", 1, VARIANCE) | ||||
|             else: | ||||
|                 logging.warning("Some database functions will not work without NumPy support") | ||||
|         else: | ||||
|             raise FpdbError("unrecognised database backend:"+backend) | ||||
| 
 | ||||
|         self.cursor = self.db.cursor() | ||||
|         # Set up query dictionary as early in the connection process as we can. | ||||
|         self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) | ||||
|         self.cursor.execute(self.sql.query['set tx level']) | ||||
|         self.wrongDbVersion = False | ||||
|         try: | ||||
|             self.cursor.execute("SELECT * FROM Settings") | ||||
|             settings = self.cursor.fetchone() | ||||
|             if settings[0] != 118: | ||||
|                 print "outdated or too new database version - please recreate tables" | ||||
|                 self.wrongDbVersion = True | ||||
|         except:# _mysql_exceptions.ProgrammingError: | ||||
|             if database !=  ":memory:": print "failed to read settings table - please recreate tables" | ||||
|             self.wrongDbVersion = True | ||||
|     #end def connect | ||||
| 
 | ||||
|     def disconnect(self, due_to_error=False): | ||||
|         """Disconnects the DB""" | ||||
|         if due_to_error: | ||||
|             self.db.rollback() | ||||
|         else: | ||||
|             self.db.commit() | ||||
|         self.cursor.close() | ||||
|         self.db.close() | ||||
|     #end def disconnect | ||||
| 
 | ||||
|     def reconnect(self, due_to_error=False): | ||||
|         """Reconnects the DB""" | ||||
|         #print "started fpdb_db.reconnect" | ||||
|         self.disconnect(due_to_error) | ||||
|         self.connect(self.backend, self.host, self.database, self.user, self.password) | ||||
| 
 | ||||
|     def get_backend_name(self): | ||||
|         """Returns the name of the currently used backend""" | ||||
|         if self.backend==2: | ||||
|             return "MySQL InnoDB" | ||||
|         elif self.backend==3: | ||||
|             return "PostgreSQL" | ||||
|         elif self.backend==4: | ||||
|             return "SQLite" | ||||
|         else: | ||||
|             raise FpdbError("invalid backend") | ||||
|     #end def get_backend_name | ||||
| 
 | ||||
|     def get_db_info(self): | ||||
|         return (self.host, self.database, self.user, self.password) | ||||
|     #end def get_db_info | ||||
| 
 | ||||
| #end class fpdb_db | ||||
|  |  | |||
|  | @ -35,7 +35,6 @@ import gtk | |||
| 
 | ||||
| #    fpdb/FreePokerTools modules | ||||
| 
 | ||||
| import fpdb_db | ||||
| import Database | ||||
| import Configuration | ||||
| import Exceptions | ||||
|  |  | |||
							
								
								
									
										0
									
								
								pyfpdb/test_PokerStars.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								pyfpdb/test_PokerStars.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										5
									
								
								run_fpdb.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										5
									
								
								run_fpdb.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							|  | @ -19,8 +19,11 @@ | |||
| import os | ||||
| import sys | ||||
| 
 | ||||
| # sys.path[0] holds the dir run_fpdb.py was in | ||||
| # sys.path[0] holds the directory run_fpdb.py is in | ||||
| sys.path[0] = sys.path[0]+os.sep+"pyfpdb" | ||||
| os.chdir(sys.path[0]) | ||||
| #print "sys.path[0] =", sys.path[0], "cwd =", os.getcwd() | ||||
| 
 | ||||
| 
 | ||||
| import fpdb | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user