From d08e9a1ea317830c3c40dc79802e775d1f00b10c Mon Sep 17 00:00:00 2001 From: Worros Date: Sun, 22 Nov 2009 12:34:11 +0800 Subject: [PATCH 01/14] Add support for UTF-8 HH type. Able to parse and import an EUR hh file --- pyfpdb/PokerStarsToFpdb.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py index 7f242789..7e63cee6 100755 --- a/pyfpdb/PokerStarsToFpdb.py +++ b/pyfpdb/PokerStarsToFpdb.py @@ -31,18 +31,18 @@ class PokerStars(HandHistoryConverter): sitename = "PokerStars" filetype = "text" - codepage = "cp1252" + codepage = ("utf8", "cp1252") siteId = 2 # Needs to match id entry in Sites database mixes = { 'HORSE': 'horse', '8-Game': '8game', 'HOSE': 'hose'} # Legal mixed games - sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\x80", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE + sym = {'USD': "\$", 'CAD': "\$", 'T$': "", "EUR": "\xe2\x82\xac", "GBP": "\xa3"} # ADD Euro, Sterling, etc HERE substitutions = { 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes - 'LS' : "\$|\x80|\xa3" # legal currency symbols ADD Euro, Sterling, etc HERE + 'LS' : "\$|\xe2\x82\xac|" # legal currency symbols - Euro(cp1252, utf-8) } # Static regexes - re_GameInfo = re.compile(""" + re_GameInfo = re.compile(u""" PokerStars\sGame\s\#(?P[0-9]+):\s+ (Tournament\s\# # open paren of tournament info (?P\d+),\s @@ -62,7 +62,7 @@ class PokerStars(HandHistoryConverter): (?P.*$)""" % substitutions, re.MULTILINE|re.VERBOSE) - re_PlayerInfo = re.compile(""" + re_PlayerInfo = re.compile(u""" ^Seat\s(?P[0-9]+):\s (?P.*)\s \((%(LS)s)?(?P[.0-9]+)\sin\schips\)""" % substitutions, @@ -373,12 +373,9 @@ if __name__ == "__main__": parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="regression-test-files/stars/horse/HH20090226 Natalie V - $0.10-$0.20 - HORSE.txt") 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") + #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() From 64d9a3582b21b10de5d4b50286351892936fce8a Mon Sep 17 00:00:00 2001 From: Eric Blade Date: Sun, 22 Nov 2009 00:00:23 -0500 Subject: [PATCH 02/14] No longer mess with sys.argv (messing with system variables is a bad thing, right), use argv to determine pathname of executeable as sys.path[0] is just the first component of the path. also all database errors except MySQL reporting "Access Denied" should now crash FPDB, so someone can fill those into Exceptions, and into the fpdb_db and the fpdb files. Process: get crash info, add exception info to Exceptions.py, catch generic database exception in fpdb_db.py (around the connect line), throw correct Fpdb exception, then catch it in fpdb.py and do the appropriate thing on the GUI end. --- pyfpdb/Configuration.py | 98 +++++++++++++++++++++-------------------- pyfpdb/Exceptions.py | 11 ++++- pyfpdb/HUD_main.py | 36 +++++++-------- pyfpdb/Options.py | 18 ++++---- pyfpdb/fpdb.py | 84 ++++++++++++++++++----------------- pyfpdb/fpdb_db.py | 27 +++++++----- 6 files changed, 146 insertions(+), 128 deletions(-) diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index 3fa5cc1d..eec8e146 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -6,17 +6,17 @@ Handles HUD configuration files. """ # Copyright 2008, 2009, 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 @@ -54,16 +54,20 @@ def get_exec_path(): """Returns the path to the fpdb.(py|exe) file we are executing""" if hasattr(sys, "frozen"): # compiled by py2exe return os.path.dirname(sys.executable) - else: - return os.path.dirname(sys.path[0]) + else: + print "argv=", sys.argv + pathname = os.path.dirname(sys.argv[0]) + return os.path.abspath(pathname) 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): return config_path @@ -142,7 +146,7 @@ class Layout: if node.hasAttribute('fav_seat'): self.fav_seat = int( node.getAttribute('fav_seat') ) self.width = int( node.getAttribute('width') ) self.height = int( node.getAttribute('height') ) - + self.location = [] self.location = map(lambda x: None, range(self.max+1)) # fill array with max seats+1 empty entries @@ -161,7 +165,7 @@ class Layout: temp = temp + " Locations = " for i in range(1, len(self.location)): temp = temp + "(%d,%d)" % self.location[i] - + return temp + "\n" class Site: @@ -171,7 +175,7 @@ class Site: if os.path.exists(path): return os.path.abspath(path) return path - + self.site_name = node.getAttribute("site_name") self.table_finder = node.getAttribute("table_finder") self.screen_name = node.getAttribute("screen_name") @@ -191,7 +195,7 @@ class Site: self.ypad = node.getAttribute("ypad") self.layout = {} - print "Loading site", self.site_name + print "Loading site", self.site_name for layout_node in node.getElementsByTagName('layout'): lo = Layout(layout_node) @@ -204,7 +208,7 @@ class Site: self.hudopacity = 1.0 if self.hudopacity == "" else float(self.hudopacity) if self.use_frames == "": self.use_frames = False - if self.font == "": self.font = "Sans" + if self.font == "": self.font = "Sans" if self.hudbgcolor == "": self.hudbgcolor = "#000000" if self.hudfgcolor == "": self.hudfgcolor = "#FFFFFF" @@ -216,20 +220,20 @@ class Site: value = getattr(self, key) if callable(value): continue temp = temp + ' ' + key + " = " + str(value) + "\n" - + for layout in self.layout: temp = temp + "%s" % self.layout[layout] - + return temp - + class Stat: def __init__(self): pass - + def __str__(self): temp = " stat_name = %s, row = %d, col = %d, tip = %s, click = %s, popup = %s\n" % (self.stat_name, self.row, self.col, self.tip, self.click, self.popup) return temp - + class Game: def __init__(self, node): self.game_name = node.getAttribute("game_name") @@ -262,9 +266,9 @@ class Game: stat.hudprefix = stat_node.getAttribute("hudprefix") stat.hudsuffix = stat_node.getAttribute("hudsuffix") stat.hudcolor = stat_node.getAttribute("hudcolor") - + self.stats[stat.stat_name] = stat - + def __str__(self): temp = "Game = " + self.game_name + "\n" temp = temp + " rows = %d\n" % self.rows @@ -272,12 +276,12 @@ class Game: temp = temp + " xpad = %d\n" % self.xpad temp = temp + " ypad = %d\n" % self.ypad temp = temp + " aux = %s\n" % self.aux - + for stat in self.stats.keys(): temp = temp + "%s" % self.stats[stat] - + return temp - + class Database: def __init__(self, node): self.db_name = node.getAttribute("db_name") @@ -288,7 +292,7 @@ class Database: self.db_selected = string_to_bool(node.getAttribute("default"), default=False) log.debug("Database db_name:'%(name)s' db_server:'%(server)s' db_ip:'%(ip)s' db_user:'%(user)s' db_pass (not logged) selected:'%(sel)s'" \ % { 'name':self.db_name, 'server':self.db_server, 'ip':self.db_ip, 'user':self.db_user, 'sel':self.db_selected} ) - + def __str__(self): temp = 'Database = ' + self.db_name + '\n' for key in dir(self): @@ -336,7 +340,7 @@ class Popup: self.pu_stats = [] for stat_node in node.getElementsByTagName('pu_stat'): self.pu_stats.append(stat_node.getAttribute("pu_stat_name")) - + def __str__(self): temp = "Popup = " + self.name + "\n" for stat in self.pu_stats: @@ -385,7 +389,7 @@ class Tv: self.combinedPostflop = string_to_bool(node.getAttribute("combinedPostflop"), default=True) def __str__(self): - return (" combinedStealFold = %s\n combined2B3B = %s\n combinedPostflop = %s\n" % + return (" combinedStealFold = %s\n combined2B3B = %s\n combinedPostflop = %s\n" % (self.combinedStealFold, self.combined2B3B, self.combinedPostflop) ) class Config: @@ -410,7 +414,7 @@ class Config: print "\nReading configuration file %s\n" % file try: doc = xml.dom.minidom.parse(file) - except: + except: log.error("Error parsing %s. See error log file." % (file)) traceback.print_exc(file=sys.stderr) print "press enter to continue" @@ -438,9 +442,9 @@ class Config: for game_node in doc.getElementsByTagName("game"): game = Game(node = game_node) self.supported_games[game.game_name] = game - + # parse databases defined by user in the section - # the user may select the actual database to use via commandline or by setting the selected="bool" + # the user may select the actual database to use via commandline or by setting the selected="bool" # attribute of the tag. if no database is explicitely selected, we use the first one we come across # s_dbs = doc.getElementsByTagName("supported_databases") #TODO: do we want to take all tags or all tags contained in @@ -452,7 +456,7 @@ class Config: if self.db_selected is None or db.db_selected: self.db_selected = db.db_name self.supported_databases[db.db_name] = db - #TODO: if the user may passes '' (empty string) as database name via command line, his choice is ignored + #TODO: if the user may passes '' (empty string) as database name via command line, his choice is ignored # ..when we parse the xml we allow for ''. there has to be a decission if to allow '' or not if dbname and dbname in self.supported_databases: self.db_selected = dbname @@ -502,7 +506,7 @@ class Config: def set_hhArchiveBase(self, path): self.imp.node.setAttribute("hhArchiveBase", path) - + def find_default_conf(self): if os.name == 'posix': config_path = os.path.join(os.path.expanduser("~"), '.fpdb', 'default.conf') @@ -534,7 +538,7 @@ class Config: def get_layout_node(self, site_node, layout): for layout_node in site_node.getElementsByTagName("layout"): - if layout_node.getAttribute("max") is None: + if layout_node.getAttribute("max") is None: return None if int( layout_node.getAttribute("max") ) == int( layout ): return layout_node @@ -618,7 +622,7 @@ class Config: elif self.supported_databases[name].db_server== DATABASE_TYPE_POSTGRESQL: db['db-backend'] = 3 elif self.supported_databases[name].db_server== DATABASE_TYPE_SQLITE: - db['db-backend'] = 4 + db['db-backend'] = 4 else: raise ValueError('Unsupported database backend: %s' % self.supported_databases[name].db_server) return db @@ -639,7 +643,7 @@ class Config: if db_server is not None: self.supported_databases[db_name].dp_server = db_server if db_type is not None: self.supported_databases[db_name].dp_type = db_type return - + def getDefaultSite(self): "Returns first enabled site or None" for site_name,site in self.supported_sites.iteritems(): @@ -702,7 +706,7 @@ class Config: return hui - + def get_import_parameters(self): imp = {} try: imp['callFpdbHud'] = self.imp.callFpdbHud @@ -728,10 +732,10 @@ class Config: path = os.path.expanduser(self.supported_sites[site].HH_path) assert(os.path.isdir(path) or os.path.isfile(path)) # maybe it should try another site? paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = path - except AssertionError: + except AssertionError: paths['hud-defaultPath'] = paths['bulkImport-defaultPath'] = "** ERROR DEFAULT PATH IN CONFIG DOES NOT EXIST **" return paths - + def get_frames(self, site = "PokerStars"): if site not in self.supported_sites: return False return self.supported_sites[site].use_frames == True @@ -751,7 +755,7 @@ class Config: else: colors['hudfgcolor'] = self.supported_sites[site].hudfgcolor return colors - + def get_default_font(self, site='PokerStars'): font = "Sans" font_size = "8" @@ -770,17 +774,17 @@ class Config: if location is not None: return location.location return ( - ( 0, 0), (684, 61), (689, 239), (692, 346), + ( 0, 0), (684, 61), (689, 239), (692, 346), (586, 393), (421, 440), (267, 440), ( 0, 361), - ( 0, 280), (121, 280), ( 46, 30) + ( 0, 280), (121, 280), ( 46, 30) ) - + def get_aux_locations(self, aux = "mucked", max = "9"): - + try: locations = self.aux_windows[aux].layout[max].location except: - locations = ( ( 0, 0), (684, 61), (689, 239), (692, 346), + locations = ( ( 0, 0), (684, 61), (689, 239), (692, 346), (586, 393), (421, 440), (267, 440), ( 0, 361), ( 0, 280), (121, 280), ( 46, 30) ) return locations @@ -791,7 +795,7 @@ class Config: return self.supported_sites.keys() else: return [site_name for (site_name, site) in self.supported_sites.items() if site.enabled] - + def get_site_parameters(self, site): """Returns a dict of the site parameters for the specified site""" parms = {} @@ -814,7 +818,7 @@ class Config: return parms def set_site_parameters(self, site_name, converter = None, decoder = None, - hudbgcolor = None, hudfgcolor = None, + hudbgcolor = None, hudfgcolor = None, hudopacity = None, screen_name = None, site_path = None, table_finder = None, HH_path = None, enabled = None, @@ -852,7 +856,7 @@ class Config: return param return None - + def get_game_parameters(self, name): """Get the configuration parameters for the named game.""" param = {} @@ -878,7 +882,7 @@ class Config: if __name__== "__main__": c = Config() - + print "\n----------- SUPPORTED SITES -----------" for s in c.supported_sites.keys(): print c.supported_sites[s] @@ -905,7 +909,7 @@ if __name__== "__main__": for w in c.hhcs.keys(): print c.hhcs[w] print "----------- END HAND HISTORY CONVERTERS -----------" - + print "\n----------- POPUP WINDOW FORMATS -----------" for w in c.popup_windows.keys(): print c.popup_windows[w] @@ -921,7 +925,7 @@ if __name__== "__main__": c.edit_layout("PokerStars", 6, locations=( (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6) )) c.save(file="testout.xml") - + print "db = ", c.get_db_parameters() # print "tv = ", c.get_tv_parameters() # print "imp = ", c.get_import_parameters() @@ -932,7 +936,7 @@ if __name__== "__main__": print c.get_aux_parameters(mw) print "mucked locations =", c.get_aux_locations('mucked', 9) -# c.edit_aux_layout('mucked', 9, locations = [(487, 113), (555, 469), (572, 276), (522, 345), +# c.edit_aux_layout('mucked', 9, locations = [(487, 113), (555, 469), (572, 276), (522, 345), # (333, 354), (217, 341), (150, 273), (150, 169), (230, 115)]) # print "mucked locations =", c.get_aux_locations('mucked', 9) diff --git a/pyfpdb/Exceptions.py b/pyfpdb/Exceptions.py index f7e9fa54..87015e3e 100644 --- a/pyfpdb/Exceptions.py +++ b/pyfpdb/Exceptions.py @@ -4,7 +4,7 @@ class FpdbError(Exception): def __str__(self): return repr(self.value) -class FpdbParseError(FpdbError): +class FpdbParseError(FpdbError): def __init__(self,value='',hid=''): self.value = value self.hid = hid @@ -17,8 +17,15 @@ class FpdbParseError(FpdbError): class FpdbDatabaseError(FpdbError): pass -class FpdbMySQLFailedError(FpdbDatabaseError): +class FpdbMySQLError(FpdbDatabaseError): pass +class FpdbMySQLAccessDenied(FpdbDatabaseError): + def __init__(self, value='', errmsg=''): + self.value = value + self.errmsg = errmsg + def __str__(self): + return repr(self.value +" " + self.errmsg) + class DuplicateError(FpdbError): pass diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 04861e5a..7965f0b5 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -5,17 +5,17 @@ Main for FreePokerTools HUD. """ # Copyright 2008, 2009, 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 @@ -33,7 +33,7 @@ import os import Options import traceback -(options, sys.argv) = Options.fpdb_options() +(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_." @@ -101,20 +101,20 @@ class HUD_main(object): def create_HUD(self, new_hand_id, table, table_name, max, poker_game, type, stat_dict, cards): """type is "ring" or "tour" used to set hud_params""" - + def idle_func(): - + gtk.gdk.threads_enter() try: # TODO: seriously need to decrease the scope of this block.. what are we expecting to error? # TODO: The purpose of this try/finally block is to make darn sure that threads_leave() - # TODO: gets called. If there is an exception and threads_leave() doesn't get called we + # TODO: gets called. If there is an exception and threads_leave() doesn't get called we # TODO: lock up. REB table.gdkhandle = gtk.gdk.window_foreign_new(table.number) newlabel = gtk.Label("%s - %s" % (table.site, table_name)) self.vb.add(newlabel) newlabel.show() self.main_window.resize_children() - + self.hud_dict[table_name].tablehudlabel = newlabel self.hud_dict[table_name].create(new_hand_id, self.config, stat_dict, cards) for m in self.hud_dict[table_name].aux_windows: @@ -151,23 +151,23 @@ class HUD_main(object): [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table_name].aux_windows] gobject.idle_add(idle_func) - + def update_HUD(self, new_hand_id, table_name, config): """Update a HUD gui from inside the non-gui read_stdin thread.""" # This is written so that only 1 thread can touch the gui--mainly -# for compatibility with Windows. This method dispatches the +# for compatibility with Windows. This method dispatches the # function idle_func() to be run by the gui thread, at its leisure. def idle_func(): gtk.gdk.threads_enter() -# try: +# try: self.hud_dict[table_name].update(new_hand_id, config) [aw.update_gui(new_hand_id) for aw in self.hud_dict[table_name].aux_windows] # finally: gtk.gdk.threads_leave() return False - + gobject.idle_add(idle_func) - + def read_stdin(self): # This is the thread function """Do all the non-gui heavy lifting for the HUD program.""" @@ -176,7 +176,7 @@ class HUD_main(object): # need their own access to the database, but should open their own # if it is required. self.db_connection = Database.Database(self.config) - + # get hero's screen names and player ids self.hero, self.hero_ids = {}, {} for site in self.config.get_supported_sites(): @@ -185,7 +185,7 @@ class HUD_main(object): site_id = result[0][0] self.hero[site_id] = self.config.supported_sites[site].screen_name self.hero_ids[site_id] = self.db_connection.get_player_id(self.config, site, self.hero[site_id]) - + while 1: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() new_hand_id = string.rstrip(new_hand_id) @@ -199,7 +199,7 @@ class HUD_main(object): (table_name, max, poker_game, type, site_id, site_name, tour_number, tab_number) = \ self.db_connection.get_table_info(new_hand_id) except Exception, err: - print "db error: skipping %s" % new_hand_id + print "db error: skipping %s" % new_hand_id sys.stderr.write("Database error: could not find hand %s.\n" % new_hand_id) continue @@ -210,7 +210,7 @@ class HUD_main(object): # Update an existing HUD if temp_key in self.hud_dict: - # get stats using hud's specific params and get cards + # get stats using hud's specific params and get cards self.db_connection.init_hud_stat_vars( self.hud_dict[temp_key].hud_params['hud_days'] , self.hud_dict[temp_key].hud_params['h_hud_days']) stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_dict[temp_key].hud_params, self.hero_ids[site_id]) @@ -222,7 +222,7 @@ class HUD_main(object): self.hud_dict[temp_key].cards = cards [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows] self.update_HUD(new_hand_id, temp_key, self.config) - + # Or create a new HUD else: # get stats using default params--also get cards diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py index 8cd98f20..fadce2fd 100644 --- a/pyfpdb/Options.py +++ b/pyfpdb/Options.py @@ -23,20 +23,20 @@ def fpdb_options(): """Process command line options for fpdb and HUD_main.""" parser = OptionParser() - parser.add_option("-x", "--errorsToConsole", - action="store_true", + parser.add_option("-x", "--errorsToConsole", + action="store_true", help="If passed error output will go to the console rather than .") - parser.add_option("-d", "--databaseName", + parser.add_option("-d", "--databaseName", dest="dbname", default="fpdb", help="Overrides the default database name") - parser.add_option("-c", "--configFile", + parser.add_option("-c", "--configFile", dest="config", default=None, help="Specifies a configuration file.") - parser.add_option("-r", "--rerunPython", - action="store_true", + parser.add_option("-r", "--rerunPython", + action="store_true", help="Indicates program was restarted with a different path (only allowed once).") - (options, sys.argv) = parser.parse_args() - return (options, sys.argv) + (options, argv) = parser.parse_args() + return (options, argv) if __name__== "__main__": (options, sys.argv) = fpdb_options() @@ -45,4 +45,4 @@ if __name__== "__main__": print "config file =", options.config print "press enter to end" - sys.stdin.readline() \ No newline at end of file + sys.stdin.readline() diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 97cf8cf5..d0a5b815 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -41,7 +41,7 @@ if os.name == 'nt' and sys.version[0:3] not in ('2.5', '2.6') and '-r' not in sy else: pass #print "debug - not changing path" - + if os.name == 'nt': import win32api import win32con @@ -53,7 +53,7 @@ import threading import Options import string cl_options = string.join(sys.argv[1:]) -(options, sys.argv) = Options.fpdb_options() +(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_." @@ -80,7 +80,7 @@ import SQL import Database import FpdbSQLQueries import Configuration -from Exceptions import * +import Exceptions VERSION = "0.12" @@ -178,23 +178,23 @@ class fpdb: """obtains db root credentials from user""" self.warning_box("Unimplemented: Get Root Database Credentials") # user, pw=None, None -# +# # dialog=gtk.Dialog(title="DB Credentials needed", parent=None, flags=0, # buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,"Connect and recreate",gtk.RESPONSE_OK)) -# +# # label_warning1=gtk.Label("Please enter credentials for a database user for "+self.host+" that has permissions to create a database.") -# -# +# +# # label_user=gtk.Label("Username") # dialog.vbox.add(label_user) # label_user.show() -# +# # response=dialog.run() # dialog.destroy() # return (user, pw, response) def dia_import_db(self, widget, data=None): - self.warning_box("Unimplemented: Import Database") + self.warning_box("Unimplemented: Import Database") self.obtain_global_lock() self.release_global_lock() @@ -211,7 +211,7 @@ class fpdb: # chooser.set_filename(self.profile) # response = chooser.run() - # chooser.destroy() + # chooser.destroy() # if response == gtk.RESPONSE_OK: # self.load_profile(chooser.get_filename()) # elif response == gtk.RESPONSE_CANCEL: @@ -239,7 +239,7 @@ class fpdb: dia_confirm.destroy() if response == gtk.RESPONSE_YES: #if self.db.backend == self.fdb_lock.fdb.MYSQL_INNODB: - # mysql requires locks on all tables or none - easier to release this lock + # mysql requires locks on all tables or none - easier to release this lock # than lock all the other tables # ToDo: lock all other tables so that lock doesn't have to be released # self.release_global_lock() @@ -252,7 +252,7 @@ class fpdb: print 'User cancelled recreating tables' #if not lock_released: self.release_global_lock() - + def dia_recreate_hudcache(self, widget, data=None): if self.obtain_global_lock(): self.dia_confirm = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm recreating HUD cache") @@ -314,7 +314,7 @@ class fpdb: entry.set_text(ds) win.destroy() self.dia_confirm.set_modal(True) - + def dia_regression_test(self, widget, data=None): self.warning_box("Unimplemented: Regression Test") self.obtain_global_lock() @@ -322,7 +322,7 @@ class fpdb: def dia_save_profile(self, widget, data=None): self.warning_box("Unimplemented: Save Profile (try saving a HUD layout, that should do it)") - + def diaSetupWizard(self, path): diaSetupWizard = gtk.Dialog(title="Fatal Error - Config File Missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) @@ -453,21 +453,25 @@ class fpdb: self.sql = SQL.Sql(db_server = self.settings['db-server']) try: self.db = Database.Database(self.config, sql = self.sql) - except FpdbMySQLFailedError: - self.warning_box("Unable to connect to MySQL! Is the MySQL server running?!", "FPDB ERROR") + except Exceptions.FpdbMySQLAccessDenied: + self.warning_box("MySQL Server reports: Access denied. Are your permissions set correctly?") exit() - except FpdbError: - #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']) - self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR") - err = traceback.extract_tb(sys.exc_info()[2])[-1] - print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) - sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])) - except: - #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']) - self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR") - err = traceback.extract_tb(sys.exc_info()[2])[-1] - print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) - sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])) + +# except FpdbMySQLFailedError: +# self.warning_box("Unable to connect to MySQL! Is the MySQL server running?!", "FPDB ERROR") +# exit() +# except FpdbError: +# #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']) +# self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR") +# err = traceback.extract_tb(sys.exc_info()[2])[-1] +# print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) +# sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])) +# except: +# #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']) +# self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR") +# err = traceback.extract_tb(sys.exc_info()[2])[-1] +# print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) +# sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])) if self.db.wrongDbVersion: diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) @@ -496,7 +500,7 @@ class fpdb: # Database connected to successfully, load queries to pass on to other classes self.db.rollback() - + self.validate_config() def not_implemented(self, widget, data=None): @@ -624,7 +628,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window.show() self.load_profile() - + self.statusIcon = gtk.StatusIcon() if os.path.exists('../gfx/fpdb-cards.png'): self.statusIcon.set_from_file('../gfx/fpdb-cards.png') @@ -643,19 +647,19 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.statusMenu.append(menuItem) self.statusIcon.connect('popup-menu', self.statusicon_menu, self.statusMenu) self.statusIcon.set_visible(True) - + self.window.connect('window-state-event', self.window_state_event_cb) sys.stderr.write("fpdb starting ...") - + def window_state_event_cb(self, window, event): print "window_state_event", event if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: # -20 = GWL_EXSTYLE can't find it in the pywin32 libs #bits = win32api.GetWindowLong(self.window.window.handle, -20) #bits = bits ^ (win32con.WS_EX_TOOLWINDOW | win32con.WS_EX_APPWINDOW) - + #win32api.SetWindowLong(self.window.window.handle, -20, bits) - + if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: self.window.hide() self.window.set_skip_taskbar_hint(True) @@ -665,7 +669,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.window.set_skip_pager_hint(False) # Tell GTK not to propagate this signal any further return True - + def statusicon_menu(self, widget, button, time, data = None): # we don't need to pass data here, since we do keep track of most all # our variables .. the example code that i looked at for this @@ -676,7 +680,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") data.show_all() data.popup(None, None, None, 3, time) pass - + def statusicon_activate(self, widget, data = None): # Let's allow the tray icon to toggle window visibility, the way # most other apps work @@ -686,7 +690,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") else: self.window.show() self.window.present() - + def warning_box(self, str, diatitle="FPDB WARNING"): diaWarning = gtk.Dialog(title=diatitle, parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) @@ -697,7 +701,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") response = diaWarning.run() diaWarning.destroy() return response - + def validate_config(self): hhbase = self.config.get_import_parameters().get("hhArchiveBase") hhbase = os.path.expanduser(hhbase) @@ -716,7 +720,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.warning_box("WARNING: Unable to create hand output directory. Importing is not likely to work until this is fixed.") elif response == gtk.RESPONSE_NO: self.select_hhArchiveBase() - + def select_hhArchiveBase(self, widget=None): fc = gtk.FileChooserDialog(title="Select HH Output Directory", parent=None, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_OPEN,gtk.RESPONSE_OK), backend=None) fc.run() @@ -726,7 +730,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.config.save() self.load_profile() # we can't do this at the end of this func because load_profile calls this func fc.destroy() # TODO: loop this to make sure we get valid data back from it, because the open directory thing in GTK lets you select files and not select things and other stupid bullshit - + def main(self): gtk.main() return 0 diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index f3dcd681..7a2ea4fe 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -82,10 +82,10 @@ class fpdb_db: self.connect(backend=db['db-backend'], host=db['db-host'], database=db['db-databaseName'], - user=db['db-user'], + 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""" @@ -100,12 +100,15 @@ class fpdb_db: 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) + try: + self.db = MySQLdb.connect(host=host, user=user, passwd=password, db=database, use_unicode=True) #TODO: Add port option -# except: -# raise FpdbMySQLFailedError("MySQL connection failed") - elif backend==fpdb_db.PGSQL: + except MySQLdb.Error, ex: + if ex.args[0] == 1045: + raise FpdbMySQLAccessDenied(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: @@ -131,8 +134,8 @@ class fpdb_db: if not connected: try: self.db = psycopg2.connect(host = host, - user = user, - password = password, + user = user, + password = password, database = database) except: msg = "PostgreSQL connection to database (%s) user (%s) failed. Are you sure the DB is running?" % (database, user) @@ -187,13 +190,13 @@ class fpdb_db: 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: @@ -205,7 +208,7 @@ class fpdb_db: 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 From 943e188a4d3b45319a2d637ad81ecbf23cd8eba9 Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 23 Nov 2009 00:05:23 +0800 Subject: [PATCH 03/14] [NEWIMPORT] Hole card data for flop games Take some code from Grindi to calculate playersAtStreetX and streetXRaises --- pyfpdb/Database.py | 16 ++++++--- pyfpdb/DerivedStats.py | 73 ++++++++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 84c2d7a7..040b1417 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1385,6 +1385,10 @@ class Database: pids[p], pdata[p]['startCash'], pdata[p]['seatNo'], + pdata[p]['card1'], + pdata[p]['card2'], + pdata[p]['card3'], + pdata[p]['card4'], pdata[p]['winnings'], pdata[p]['street0VPI'], pdata[p]['street1Seen'], @@ -1403,6 +1407,10 @@ class Database: playerId, startCash, seatNo, + card1, + card2, + card3, + card4, winnings, street0VPI, street1Seen, @@ -1416,6 +1424,7 @@ class Database: street4Aggr ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s @@ -1423,10 +1432,9 @@ class Database: # position, # tourneyTypeId, -# card1, -# card2, -# card3, -# card4, +# card5, +# card6, +# card7, # startCards, # rake, # totalProfit, diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 43220c4b..c8dcfef8 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -81,12 +81,13 @@ class DerivedStats(): self.hands['boardcard5'] = cards[4] #print "DEBUG: self.getStreetTotals = (%s, %s, %s, %s, %s)" % hand.getStreetTotals() - #FIXME: Pot size still in decimal, needs to be converted to cents - (self.hands['street1Pot'], - self.hands['street2Pot'], - self.hands['street3Pot'], - self.hands['street4Pot'], - self.hands['showdownPot']) = hand.getStreetTotals() + totals = hand.getStreetTotals() + totals = [int(100*i) for i in totals] + self.hands['street1Pot'] = totals[0] + self.hands['street2Pot'] = totals[1] + self.hands['street3Pot'] = totals[2] + self.hands['street4Pot'] = totals[3] + self.hands['showdownPot'] = totals[4] self.vpip(hand) # Gives playersVpi (num of players vpip) #print "DEBUG: vpip: %s" %(self.hands['playersVpi']) @@ -115,6 +116,26 @@ class DerivedStats(): for i, street in enumerate(hand.actionStreets[1:]): self.aggr(self.hand, i) + default_holecards = ["Xx", "Xx", "Xx", "Xx"] + for street in hand.holeStreets: + for player in hand.players: + if player[1] in hand.holecards[street].keys(): + self.handsplayers[player[1]]['card1'] = Card.encodeCard(hand.holecards[street][player[1]][1][0]) + self.handsplayers[player[1]]['card2'] = Card.encodeCard(hand.holecards[street][player[1]][1][1]) + try: + self.handsplayers[player[1]]['card3'] = Card.encodeCard(hand.holecards[street][player[1]][1][2]) + self.handsplayers[player[1]]['card4'] = Card.encodeCard(hand.holecards[street][player[1]][1][3]) + except IndexError: + self.handsplayers[player[1]]['card3'] = 0 + self.handsplayers[player[1]]['card4'] = 0 + #print "DEBUG: hand.holecards[%s][%s][0]: %s" % (street, player[1], hand.holecards[street][player[1]][1]) + else: + #print "DEBUG: player doesn't exist, setting card1-4 to 0" + self.handsplayers[player[1]]['card1'] = 0 + self.handsplayers[player[1]]['card2'] = 0 + self.handsplayers[player[1]]['card3'] = 0 + self.handsplayers[player[1]]['card4'] = 0 + def assembleHudCache(self, hand): pass @@ -147,14 +168,19 @@ class DerivedStats(): self.hands['playersAtStreet4'] = 0 self.hands['playersAtShowdown'] = 0 + alliners = set() for (i, street) in enumerate(hand.actionStreets[2:]): - actors = {} - for act in hand.actions[street]: - actors[act[0]] = 1 - self.hands['playersAtStreet%s' % str(i+1)] = len(actors.keys()) - - #Need playersAtShowdown + actors = set() + for action in hand.actions[street]: + if len(action) > 2 and action[-1]: # allin + alliners.add(action[0]) + actors.add(action[0]) + if len(actors)==0 and len(alliners)<2: + alliners = set() + self.hands['playersAtStreet%d' % (i+1)] = len(set.union(alliners, actors)) + actions = hand.actions[hand.actionStreets[-1]] + self.hands['playersAtShowdown'] = len(set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners)) def streetXRaises(self, hand): # self.actions[street] is a list of all actions in a tuple, contining the action as the second element @@ -162,11 +188,11 @@ class DerivedStats(): # No idea what this value is actually supposed to be # In theory its "num small bets paid to see flop/street4, including blind" which makes sense for limit. Not so useful for nl # Leaving empty for the moment, - self.hands['street0Raises'] = 0 # /* num small bets paid to see flop/street4, including blind */ - self.hands['street1Raises'] = 0 # /* num small bets paid to see turn/street5 */ - self.hands['street2Raises'] = 0 # /* num big bets paid to see river/street6 */ - self.hands['street3Raises'] = 0 # /* num big bets paid to see sd/street7 */ - self.hands['street4Raises'] = 0 # /* num big bets paid to see showdown */ + + for i in range(5): self.hands['street%dRaises' % i] = 0 + + for (i, street) in enumerate(hand.actionStreets[1:]): + self.hands['street%dRaises' % i] = len(filter( lambda action: action[1] in ('raises','bets'), hand.actions[street])) def seen(self, hand, i): pas = set() @@ -193,3 +219,16 @@ class DerivedStats(): def countPlayers(self, hand): pass + + def pfba(self, actions, f=None, l=None): + """Helper method. Returns set of PlayersFilteredByActions + + f - forbidden actions + l - limited to actions + """ + players = set() + for action in actions: + if l is not None and action[1] not in l: continue + if f is not None and action[1] in f: continue + players.add(action[0]) + return players From 34e7fb0459232bea8d8206fabb0af7f75111896e Mon Sep 17 00:00:00 2001 From: Worros Date: Mon, 23 Nov 2009 00:20:51 +0800 Subject: [PATCH 04/14] [NEWIMPORT] Fix HandsPlayers.cardX init --- pyfpdb/Database.py | 12 ++++++++---- pyfpdb/DerivedStats.py | 14 ++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 040b1417..dbd7afe1 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1389,6 +1389,9 @@ class Database: pdata[p]['card2'], pdata[p]['card3'], pdata[p]['card4'], + pdata[p]['card5'], + pdata[p]['card6'], + pdata[p]['card7'], pdata[p]['winnings'], pdata[p]['street0VPI'], pdata[p]['street1Seen'], @@ -1411,6 +1414,9 @@ class Database: card2, card3, card4, + card5, + card6, + card7, winnings, street0VPI, street1Seen, @@ -1424,7 +1430,8 @@ class Database: street4Aggr ) 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 @@ -1432,9 +1439,6 @@ class Database: # position, # tourneyTypeId, -# card5, -# card6, -# card7, # startCards, # rake, # totalProfit, diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index c8dcfef8..e4d59336 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -117,8 +117,10 @@ class DerivedStats(): self.aggr(self.hand, i) default_holecards = ["Xx", "Xx", "Xx", "Xx"] + for street in hand.holeStreets: for player in hand.players: + for i in range(1,8): self.handsplayers[player[1]]['card%d' % i] = 0 if player[1] in hand.holecards[street].keys(): self.handsplayers[player[1]]['card1'] = Card.encodeCard(hand.holecards[street][player[1]][1][0]) self.handsplayers[player[1]]['card2'] = Card.encodeCard(hand.holecards[street][player[1]][1][1]) @@ -126,16 +128,8 @@ class DerivedStats(): self.handsplayers[player[1]]['card3'] = Card.encodeCard(hand.holecards[street][player[1]][1][2]) self.handsplayers[player[1]]['card4'] = Card.encodeCard(hand.holecards[street][player[1]][1][3]) except IndexError: - self.handsplayers[player[1]]['card3'] = 0 - self.handsplayers[player[1]]['card4'] = 0 - #print "DEBUG: hand.holecards[%s][%s][0]: %s" % (street, player[1], hand.holecards[street][player[1]][1]) - else: - #print "DEBUG: player doesn't exist, setting card1-4 to 0" - self.handsplayers[player[1]]['card1'] = 0 - self.handsplayers[player[1]]['card2'] = 0 - self.handsplayers[player[1]]['card3'] = 0 - self.handsplayers[player[1]]['card4'] = 0 - + # Just means no player cards for that street/game - continue + pass def assembleHudCache(self, hand): pass From 3e7a0ef53c737d6fbde54195ff6dc70f69c6f4bf Mon Sep 17 00:00:00 2001 From: Eric Blade Date: Sun, 22 Nov 2009 16:40:56 -0500 Subject: [PATCH 05/14] remove all occurences of assigning sys.argv --- pyfpdb/CliFpdb.py | 10 +- pyfpdb/Configuration.py | 5 +- pyfpdb/DatabaseManager.py | 322 ++++++++++++++++++------------------ pyfpdb/Filters.py | 12 +- pyfpdb/GuiAutoImport.py | 23 ++- pyfpdb/GuiBulkImport.py | 19 +-- pyfpdb/Options.py | 2 +- pyfpdb/TournamentTracker.py | 58 +++---- 8 files changed, 222 insertions(+), 229 deletions(-) diff --git a/pyfpdb/CliFpdb.py b/pyfpdb/CliFpdb.py index 4e2a4361..abdddb45 100755 --- a/pyfpdb/CliFpdb.py +++ b/pyfpdb/CliFpdb.py @@ -26,7 +26,7 @@ except: diaSQLLibMissing = gtk.Dialog(title="Fatal Error - SQL interface library missing", parent=None, flags=0, buttons=(gtk.STOCK_QUIT,gtk.RESPONSE_OK)) print "Please note that the CLI importer only works with MySQL, if you use PostgreSQL this error is expected." - + import fpdb_import import fpdb_db @@ -36,9 +36,9 @@ if __name__ == "__main__": parser.add_option("-c", "--handCount", default="0", type="int", help="Number of hands to import (default 0 means unlimited)") parser.add_option("-d", "--database", default="fpdb", help="The MySQL database to use (default fpdb)") - parser.add_option("-e", "--errorFile", default="failed.txt", + parser.add_option("-e", "--errorFile", default="failed.txt", help="File to store failed hands into. (default: failed.txt) Not implemented.") - parser.add_option("-f", "--inputFile", "--file", "--inputfile", default="stdin", + parser.add_option("-f", "--inputFile", "--file", "--inputfile", default="stdin", help="The file you want to import (remember to use quotes if necessary)") parser.add_option("-m", "--minPrint", "--status", default="50", type="int", help="How often to print a one-line status report (0 means never, default is 50)") @@ -51,8 +51,8 @@ if __name__ == "__main__": parser.add_option("-x", "--failOnError", action="store_true", help="If this option is passed it quits when it encounters any error") - (options, sys.argv) = parser.parse_args() - + (options, argv) = parser.parse_args() + settings={'callFpdbHud':False, 'db-backend':2} settings['db-host']=options.server settings['db-user']=options.user diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index eec8e146..0348bc21 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -55,19 +55,18 @@ def get_exec_path(): if hasattr(sys, "frozen"): # compiled by py2exe return os.path.dirname(sys.executable) else: - print "argv=", sys.argv pathname = os.path.dirname(sys.argv[0]) return os.path.abspath(pathname) 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 +# 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 +# print "config path 2=", config_path if os.path.exists(config_path): return config_path diff --git a/pyfpdb/DatabaseManager.py b/pyfpdb/DatabaseManager.py index 1269f540..150415ce 100644 --- a/pyfpdb/DatabaseManager.py +++ b/pyfpdb/DatabaseManager.py @@ -1,6 +1,6 @@ """Database manager -@todo: (gtk) how to validate user input in gtk.Dialog? as soon as the user clicks ok the dialog is dead. we use a while loop as workaround. not nice +@todo: (gtk) how to validate user input in gtk.Dialog? as soon as the user clicks ok the dialog is dead. we use a while loop as workaround. not nice @todo: (fpdb) we need the application name 'fpdb' from somewhere to put it in dialog titles @todo: (fpdb) config object should be initialized globally and accessible from all modules via Configuration.py @@ -24,10 +24,10 @@ import gobject #******************************************************************************************************* class DatabaseManager(gobject.GObject): DatabaseTypes = {} - + @classmethod def from_fpdb(klass, data, defaultDatabaseType=None): - + #NOTE: if no databases are present in config fpdb fails with # Traceback (most recent call last): # File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/DatabaseManager.py", line 783, in @@ -38,12 +38,12 @@ class DatabaseManager(gobject.GObject): # db = self.get_db_parameters() # File "/home/me2/Scr/Repos/fpdb-mme/pyfpdb/Configuration.py", line 583, in get_db_parameters # name = self.db_selected - # AttributeError: Config instance has no attribute 'db_selected' + # AttributeError: Config instance has no attribute 'db_selected' import sys import Options import Configuration #NOTE: fpdb should perform this globally - (options, sys.argv) = Options.fpdb_options() + (options, argv) = Options.fpdb_options() config = Configuration.Config(file=options.config, dbname=options.dbname) #TODO: handle no database present defaultDatabaseName = config.get_db_parameters().get('db-databaseName', None) @@ -54,7 +54,7 @@ class DatabaseManager(gobject.GObject): #NOTE: Config does not seem to validate user input, so anything may end up here if databaseKlass is None: raise ValueError('Unknown databasetype: %s' % fpdbDatabase.db_server) - + database = databaseKlass() if database.Type == 'sqlite': database.name = fpdbDatabase.db_name @@ -66,17 +66,17 @@ class DatabaseManager(gobject.GObject): database.port = int(fpdbDatabase.db_ip) database.user = fpdbDatabase.db_user database.password = fpdbDatabase.db_pass - databases.append(database) - + databases.append(database) + return klass(databases=databases, defaultDatabaseType=defaultDatabaseType) - + def to_fpdb(self): pass - - + + def __init__(self, databases=None, defaultDatabaseType=None): gobject.GObject.__init__(self) - + self._defaultDatabaseType = defaultDatabaseType self._databases = [] if databases is None else list(databases) self._activeDatabase = None @@ -98,21 +98,21 @@ class DatabaseManager(gobject.GObject): self._databases.append(database) def remove_database(self, database): self._databases.remove(database) - + def activate_database(self, database): if self._activeDatabase is not None: self._activeDatabase.status = self._activeDatabase.StatusInactive #TODO: finalize database self.emit('database-deactivated', self.database_id(self._activeDatabase) ) - + database.status = database.StatusActive #TODO: activate database self._activeDatabase = database self.emit('database-activated', self.database_id(database) ) - + def active_database(self): return self._activeDatabase - + # register DatabaseManager signals gobject.type_register(DatabaseManager) gobject.signal_new('database-activated', DatabaseManager, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (int, )) @@ -134,20 +134,20 @@ class DatabaseTypeBase(object): StatusActive = 'active' StatusInactive = 'inactive' StatusError = 'error' #TODO: not implemented - + #TODO: not happy with returning error string. just being too lazy to impl dozens of error codes for later translation def init_new_database(self): """initializes a new empty database @return: (str) error if something goes wrong, None otherwise """ raise NotImplementedError() - + def validate_database(self): """checks if the database is valid @return: (str) error if something goes wrong, None otherwise """ raise NotImplementedError() - + class DatabaseTypePostgres(DatabaseTypeBase): Type = 'postgresql' @classmethod @@ -161,15 +161,15 @@ class DatabaseTypePostgres(DatabaseTypeBase): self.password = password self.database = database self.status = self.StatusInactive - + #TODO: implement def init_new_database(self): pass - + #TODO: implement def validate_database(self): pass - + class DatabaseTypeMysql(DatabaseTypeBase): Type = 'mysql' @classmethod @@ -183,11 +183,11 @@ class DatabaseTypeMysql(DatabaseTypeBase): self.password = password self.database = database self.status = self.StatusInactive - + #TODO: implement def init_new_database(self): pass - + #TODO: implement def validate_database(self): pass @@ -202,7 +202,7 @@ class DatabaseTypeSqLite(DatabaseTypeBase): self.name = name self.file = file self.status = self.StatusInactive - + def init_new_database(self): # make shure all attrs are specified if not self.file: @@ -212,15 +212,15 @@ class DatabaseTypeSqLite(DatabaseTypeBase): open(self.file, 'w').close() except IOError: return 'can not write file' - + #TODO: init tables (...) - - + + def validate_database(self): pass #TODO: check if tables (...) exist - - + + #TODO: how do we want to handle unsupported database types? # ..uncomment to remove unsupported database types @@ -235,7 +235,7 @@ class DatabaseTypeSqLite(DatabaseTypeBase): #TODO: there is no title (on linux), wtf? def DialogError(parent=None, msg=''): dlg = gtk.MessageDialog( - parent=parent, + parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, @@ -248,33 +248,33 @@ def DialogError(parent=None, msg=''): #TODO: derrive from gtk.VBox? class WidgetDatabaseProperties(gtk.VBox): - + ModeNew = 0 ModeEdit = 1 ModeAdd = 2 - + class SqLiteFileChooserButton(gtk.HBox): #NOTE: for some weird reason it is impossible to let the user choose a non exiting filename with gtk.FileChooserButton, so impl our own on the fly def __init__(self, widgetDatabaseProperties, parentWidget): gtk.HBox.__init__(self) self.set_homogeneous(False) - + self.parentWidget = parentWidget self.widgetDatabaseProperties = widgetDatabaseProperties self.entry = gtk.Entry() self.button = gtk.Button('...') self.button.connect('clicked', self.on_button_clicked) - + # layout widgets self.pack_start(self.entry, True, True) self.pack_start(self.button, False, False) - + def get_filename(self): return self.entry.get_text() - + def set_filename(self, name): self.entry.set_text(name) - + def on_button_clicked(self, button): if self.widgetDatabaseProperties.mode == WidgetDatabaseProperties.ModeAdd: action = gtk.FILE_CHOOSER_ACTION_OPEN @@ -283,13 +283,13 @@ class WidgetDatabaseProperties(gtk.VBox): else: raise ValueError('unsupported dialog mode') dlg = gtk.FileChooserDialog( - title='Choose an exiting database file or type in name of a new one', - parent=self.parentWidget, - action=action, + title='Choose an exiting database file or type in name of a new one', + parent=self.parentWidget, + action=action, buttons=( gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_OK, - ), + ), backend=None ) dlg.set_default_response(gtk.RESPONSE_OK) @@ -298,8 +298,8 @@ class WidgetDatabaseProperties(gtk.VBox): fileName = dlg.get_filename() self.set_filename(fileName) dlg.destroy() - - + + #TODO: bit ugly this thingy. try to find a better way to map database attrs to gtk widgets class FieldWidget(object): def __init__(self, text='', attrDatabase='', widget=None, attrGet=None, attrSet=None, defaultValue=None, canEdit=False, tooltip=''): @@ -310,15 +310,15 @@ class WidgetDatabaseProperties(gtk.VBox): self._attrDatabase = attrDatabase self._widget = widget self._defaultValue = defaultValue - self._attrGetter=None, - self._attrGet = attrGet - self._attrSet = attrSet + self._attrGetter=None, + self._attrGet = attrGet + self._attrSet = attrSet self._canEdit = canEdit - + self._label.set_tooltip_text(tooltip) self._widget.set_tooltip_text(tooltip) - - def widget(self): + + def widget(self): return self._widget def label(self): return self._label @@ -335,10 +335,10 @@ class WidgetDatabaseProperties(gtk.VBox): setattr(database, self._attrDatabase, getattr(self._widget, self._attrGet)() ) def reset_value(self): getattr(self._widget, self._attrSet)(self._defaultValue) - + def __init__(self, databaseManager, database, mode=ModeEdit, parentWidget=None): gtk.VBox.__init__(self) - + self.databaseManager = databaseManager self.database = database self.mode = mode @@ -346,76 +346,76 @@ class WidgetDatabaseProperties(gtk.VBox): self.fieldWidgets = ( self.FieldWidget( text='Name:', - attrDatabase='name', + attrDatabase='name', widget=gtk.Entry(), defaultValue='', - attrGet='get_text', - attrSet='set_text', + attrGet='get_text', + attrSet='set_text', canEdit=True, tooltip='Any name you like to name the database ' ), self.FieldWidget( - text='File:', - attrDatabase='file', - widget=self.SqLiteFileChooserButton(self, self.parentWidget), + text='File:', + attrDatabase='file', + widget=self.SqLiteFileChooserButton(self, self.parentWidget), defaultValue='', - attrGet='get_filename', - attrSet='set_filename', - canEdit=False, + attrGet='get_filename', + attrSet='set_filename', + canEdit=False, tooltip='Fully qualified path of the file to hold the database ' ), self.FieldWidget( - text='Host:', - attrDatabase='host', - widget=gtk.Entry(), + text='Host:', + attrDatabase='host', + widget=gtk.Entry(), defaultValue='', - attrGet='get_text', - attrSet='set_text', - canEdit=False, + attrGet='get_text', + attrSet='set_text', + canEdit=False, tooltip='Host the database is located at' ), self.FieldWidget( - text='Port:', - attrDatabase='port', - widget=gtk.SpinButton(adjustment=gtk.Adjustment(value=0, lower=0, upper=999999, step_incr=1, page_incr=10) ), + text='Port:', + attrDatabase='port', + widget=gtk.SpinButton(adjustment=gtk.Adjustment(value=0, lower=0, upper=999999, step_incr=1, page_incr=10) ), defaultValue=0, - attrGet='get_value', - attrSet='set_value', - canEdit=False, + attrGet='get_value', + attrSet='set_value', + canEdit=False, tooltip='Port to use to connect to the host' ), self.FieldWidget( - text='User:', - attrDatabase='user', - widget=gtk.Entry(), + text='User:', + attrDatabase='user', + widget=gtk.Entry(), defaultValue='', - attrGet='get_text', - attrSet='set_text', - canEdit=False, + attrGet='get_text', + attrSet='set_text', + canEdit=False, tooltip='User name used to login to the host' ), self.FieldWidget( - text='Pwd:', - attrDatabase='password', - widget=gtk.Entry(), + text='Pwd:', + attrDatabase='password', + widget=gtk.Entry(), defaultValue='', - attrGet='get_text', - attrSet='set_text', - canEdit=False, + attrGet='get_text', + attrSet='set_text', + canEdit=False, tooltip='Password used to login to the host' ), self.FieldWidget( - text='Db:', - attrDatabase='database', - widget=gtk.Entry(), + text='Db:', + attrDatabase='database', + widget=gtk.Entry(), defaultValue='', - attrGet='get_text', - attrSet='set_text', + attrGet='get_text', + attrSet='set_text', canEdit=False, tooltip='Name of the database' ), ) - + # setup database type combo self.comboType = gtk.ComboBox() listStore= gtk.ListStore(str, str) @@ -424,7 +424,7 @@ class WidgetDatabaseProperties(gtk.VBox): self.comboType.pack_start(cell, True) self.comboType.add_attribute(cell, 'text', 0) self.comboType.connect('changed', self.on_combo_type_changed) - + # fill database type combo with available database klasses. we store (databaseDisplayName, databaseType) in our model for later lookup iCurrentDatabase = 0 databaseTypes = [(klass.display_name(), klass.Type) for klass in databaseManager.DatabaseTypes.values()] @@ -435,7 +435,7 @@ class WidgetDatabaseProperties(gtk.VBox): iCurrentDatabase = i if self.mode == self.ModeEdit or len(databaseTypes) < 2: self.comboType.set_button_sensitivity(gtk.SENSITIVITY_OFF) - + # init and layout field widgets self.pack_start(self.comboType, False, False, 2) table = gtk.Table(rows=len(self.fieldWidgets) +1, columns=2, homogeneous=False) @@ -443,11 +443,11 @@ class WidgetDatabaseProperties(gtk.VBox): for i,fieldWidget in enumerate(self.fieldWidgets): table.attach(fieldWidget.label(), 0, 1, i, i+1, xoptions=gtk.FILL) table.attach(fieldWidget.widget(), 1, 2, i, i+1) - + # init widget self.comboType.set_active(iCurrentDatabase) self._adjust_widgets(self.database) - + def _adjust_widgets(self, database): for fieldWidget in self.fieldWidgets: isSensitive = fieldWidget.is_sensitive(database) @@ -458,24 +458,24 @@ class WidgetDatabaseProperties(gtk.VBox): if self.mode == self.ModeEdit: isSensitive = isSensitive and fieldWidget.can_edit() fieldWidget.set_sensitive(isSensitive) - - + + def on_combo_type_changed(self, combo): i = self.comboType.get_active() if i < 0: return - + # check if we need to init a new database currentDatabaseType = self.comboType.get_model()[i][1] if currentDatabaseType == self.database.Type: return - + # create new empty database #NOTE: we dont register it in DatabaseManager self.database = self.databaseManager.DatabaseTypes[currentDatabaseType]() self._adjust_widgets(self.database) - - + + def get_database(self): for fieldWidget in self.fieldWidgets: if fieldWidget.is_sensitive(self.database): @@ -486,7 +486,7 @@ class WidgetDatabaseProperties(gtk.VBox): class DialogDatabaseProperties(gtk.Dialog): def __init__(self, databaseManager, database, parent=None, mode=WidgetDatabaseProperties.ModeEdit, title=''): gtk.Dialog.__init__(self, - title=title, + title=title, parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, buttons=( @@ -495,7 +495,7 @@ class DialogDatabaseProperties(gtk.Dialog): ) ) self.connect('response', self.on_dialog_response) - + # setup widget self.widgetDatabaseProperties = WidgetDatabaseProperties(databaseManager,database, mode=mode, parentWidget=self) self.vbox.pack_start(self.widgetDatabaseProperties, True, True) @@ -503,23 +503,23 @@ class DialogDatabaseProperties(gtk.Dialog): def get_widget_database_properties(self): return self.widgetDatabaseProperties - + def on_dialog_response(self, dlg, responseId): if responseId == gtk.RESPONSE_REJECT: pass elif responseId == gtk.RESPONSE_ACCEPT: pass - -#TODO: derrive from gtk.VBox? + +#TODO: derrive from gtk.VBox? # ..is there a way to derrive from gtk.Widget or similar? this would make parentWidget kw obsolete class WidgetDatabaseManager(gtk.VBox): """ """ - + def __init__(self, databaseManager, parentWidget=None): gtk.VBox.__init__(self) - + self.parentWidget = parentWidget self.databaseManager = databaseManager self.databaseManager.connect('database-activated', self.on_database_manager_database_activated) @@ -529,17 +529,17 @@ class WidgetDatabaseManager(gtk.VBox): DatabaseTypeBase.StatusInactive: 'Inactive', DatabaseTypeBase.StatusError: 'Error', } - - + + #TODO: dono how to make word wrap work as expected self.labelInfo = gtk.Label('database management') self.labelInfo.set_line_wrap(True) self.labelInfo.set_selectable(True) self.labelInfo.set_single_line_mode(False) self.labelInfo.set_alignment(0, 0) - + # database management buttons - + #TODO: bit messy the distinction New/Add/Edit. we'd have to pass three flags to DialogDatabaseProperties # to handle this. maybe drop Edit (is just a Remove + Add), to keep things simple self.buttonDatabaseActivate = gtk.Button("Activate") @@ -560,13 +560,13 @@ class WidgetDatabaseManager(gtk.VBox): self.buttonDatabaseRemove.set_tooltip_text('removes the database from the list') self.buttonDatabaseRemove.set_sensitive(False) self.buttonDatabaseRemove.connect('clicked', self.on_button_database_remove_clicked) - + #TODO: i dont think we should do any real database management here. maybe drop it #self.buttonDatabaseDelete = gtk.Button("Delete") #self.buttonDatabaseDelete.set_tooltip_text('removes the database from the list and deletes it') #self.buttonDatabaseDelete.set_sensitive(False) - - # init database tree + + # init database tree self.treeDatabases = gtk.TreeView() treeDatabaseColumns = ( # name, displayName, dataType ('name', 'Name', str), @@ -584,7 +584,7 @@ class WidgetDatabaseManager(gtk.VBox): col.set_visible(False) self.treeDatabaseColumns[name] = i self.treeDatabases.get_selection().connect('changed', self.on_tree_databases_selection_changed) - + # layout widgets vbox = gtk.VBox(self) vbox.pack_start(self.labelInfo, False, False, 2) @@ -602,12 +602,12 @@ class WidgetDatabaseManager(gtk.VBox): #vbox.pack_start(self.buttonDatabaseDelete, False, False, 2) box = gtk.VBox() vbox.pack_start(box, True, True, 0) - + hbox.pack_start(gtk.VSeparator(), False, False, 2) hbox.pack_end(self.treeDatabases, True, True, 2) - + self.show_all() - + # init widget model = self.treeDatabases.get_model() for database in self.databaseManager: @@ -616,8 +616,8 @@ class WidgetDatabaseManager(gtk.VBox): model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] ) model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() ) model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database)) - - + + def on_database_manager_database_activated(self, databaseManager, idDatabase): database = self.databaseManager.database_from_id(idDatabase) model = self.treeDatabases.get_model() @@ -627,8 +627,8 @@ class WidgetDatabaseManager(gtk.VBox): break else: raise ValueError('database not found') - - + + def on_database_manager_database_deactivated(self, databaseManager, idDatabase): database = self.databaseManager.database_from_id(idDatabase) model = self.treeDatabases.get_model() @@ -638,31 +638,31 @@ class WidgetDatabaseManager(gtk.VBox): break else: raise ValueError('database not found') - - + + def on_button_database_activate_clicked(self, button): selection = self.treeDatabases.get_selection() if selection is None: return - + model, it = selection.get_selected() idDatabase = model.get_value(it, self.treeDatabaseColumns['_id']) database = self.databaseManager.database_from_id(idDatabase) self.databaseManager.activate_database(database) - - - #TODO: for some reason i have to click OK/Cancel twice to close the dialog + + + #TODO: for some reason i have to click OK/Cancel twice to close the dialog def on_button_database_new_clicked(self, button): databaseKlass = self.databaseManager.get_default_database_type() if databaseKlass is None: raise ValueError('no default database type set') database = databaseKlass() - + while True: dlg = DialogDatabaseProperties( - self.databaseManager, - database, - parent=self.parentWidget, + self.databaseManager, + database, + parent=self.parentWidget, mode=WidgetDatabaseProperties.ModeNew, title='New database' ) @@ -679,11 +679,11 @@ class WidgetDatabaseManager(gtk.VBox): database = None dlg.destroy() break - - + + if database is None: return - + self.databaseManager.add_database(database) model = self.treeDatabases.get_model() it = model.append() @@ -691,19 +691,19 @@ class WidgetDatabaseManager(gtk.VBox): model.set_value(it, self.treeDatabaseColumns['status'], self.databaseStatusNames[database.status] ) model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() ) model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database)) - - + + def on_button_database_add_clicked(self, button): databaseKlass = self.databaseManager.get_default_database_type() if databaseKlass is None: raise ValueError('no defult database type set') database = databaseKlass() - + while True: dlg = DialogDatabaseProperties( - self.databaseManager, - database, - parent=self.parentWidget, + self.databaseManager, + database, + parent=self.parentWidget, mode=WidgetDatabaseProperties.ModeAdd, title='Add database' ) @@ -719,11 +719,11 @@ class WidgetDatabaseManager(gtk.VBox): else: database = None dlg.destroy() - break - + break + if database is None: return - + self.databaseManager.add_database(database) model = self.treeDatabases.get_model() it = model.append() @@ -732,20 +732,20 @@ class WidgetDatabaseManager(gtk.VBox): model.set_value(it, self.treeDatabaseColumns['type'], database.display_name() ) model.set_value(it, self.treeDatabaseColumns['_id'], self.databaseManager.database_id(database)) dlg.destroy() - + def on_button_database_edit_clicked(self, button): selection = self.treeDatabases.get_selection() if selection is None: return - + model, it = selection.get_selected() idDatabase = model.get_value(it, self.treeDatabaseColumns['_id']) database = self.databaseManager.database_from_id(idDatabase) dlg = DialogDatabaseProperties( - self.databaseManager, - database, - parent=self.parentWidget, - mode=WidgetDatabaseProperties.ModeEdit, + self.databaseManager, + database, + parent=self.parentWidget, + mode=WidgetDatabaseProperties.ModeEdit, title='Edit database' ) response = dlg.run() @@ -759,31 +759,31 @@ class WidgetDatabaseManager(gtk.VBox): model.set_value(it, self.treeDatabaseColumns['name'], database.name) dlg.destroy() - + def on_button_database_remove_clicked(self, button): selection = self.treeDatabases.get_selection() if selection is None: return - + model, it = selection.get_selected() #TODO: finalize database model.remove(it) - - + + def on_tree_databases_selection_changed(self, treeSelection): hasSelection = bool(treeSelection.count_selected_rows()) - + # enable/disable selection dependend widgets self.buttonDatabaseActivate.set_sensitive(hasSelection) self.buttonDatabaseEdit.set_sensitive(hasSelection) self.buttonDatabaseRemove.set_sensitive(hasSelection) #self.buttonDatabaseDelete.set_sensitive(hasSelection) - + class DialogDatabaseManager(gtk.Dialog): def __init__(self, databaseManager, parent=None): gtk.Dialog.__init__(self, - title="Databases", + title="Databases", parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, buttons=( @@ -794,11 +794,11 @@ class DialogDatabaseManager(gtk.Dialog): self.widgetDatabaseManager = WidgetDatabaseManager(databaseManager, parentWidget=self) self.vbox.pack_start(self.widgetDatabaseManager, True, True) self.show_all() - + #************************************************************************************************** if __name__ == '__main__': databaseManager = DatabaseManager.from_fpdb('', defaultDatabaseType=DatabaseTypeSqLite) - + #d = DialogDatabaseProperties( # DatabaseManager(defaultDatabaseType=DatabaseTypeSqLite), #database=DatabaseTypePostgres(), @@ -808,5 +808,3 @@ if __name__ == '__main__': d.connect("destroy", gtk.main_quit) d.run() #gtk.main() - - diff --git a/pyfpdb/Filters.py b/pyfpdb/Filters.py index 4ee2bdf1..a61fe7d2 100644 --- a/pyfpdb/Filters.py +++ b/pyfpdb/Filters.py @@ -69,7 +69,7 @@ class Filters(threading.Thread): self.sbGroups = {} self.numHands = 0 - # Outer Packing box + # Outer Packing box self.mainVBox = gtk.VBox(False, 0) playerFrame = gtk.Frame("Hero:") @@ -332,7 +332,7 @@ class Filters(threading.Thread): elif limit == "fl": if not self.limits[limit]: # only toggle all fl limits off if they are all currently on - # this stops turning one off from cascading into 'fl' box off + # this stops turning one off from cascading into 'fl' box off # and then all fl limits being turned off all_fl_on = True for cb in self.cbLimits.values(): @@ -359,7 +359,7 @@ class Filters(threading.Thread): elif limit == "nl": if not self.limits[limit]: # only toggle all nl limits off if they are all currently on - # this stops turning one off from cascading into 'nl' box off + # this stops turning one off from cascading into 'nl' box off # and then all nl limits being turned off all_nl_on = True for cb in self.cbLimits.values(): @@ -731,11 +731,11 @@ def main(argv=None): gtk.main_quit() parser = OptionParser() - (options, sys.argv) = parser.parse_args(args = argv) + (options, argv) = parser.parse_args(args = argv) config = Configuration.Config() db = None - + db = fpdb_db.fpdb_db() db.do_connect(config) @@ -750,5 +750,3 @@ def main(argv=None): if __name__ == '__main__': sys.exit(main()) - - diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index 299c8075..c154d4c2 100755 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -53,7 +53,7 @@ class GuiAutoImport (threading.Thread): self.importer.setFailOnError(False) self.importer.setHandCount(0) # self.importer.setWatchTime() - + self.server = settings['db-host'] self.user = settings['db-user'] self.password = settings['db-password'] @@ -63,7 +63,7 @@ class GuiAutoImport (threading.Thread): hbox = gtk.HBox(True, 0) # contains 2 equal vboxes self.mainVBox.pack_start(hbox, False, False, 0) - + vbox1 = gtk.VBox(True, 0) hbox.pack_start(vbox1, True, True, 0) vbox2 = gtk.VBox(True, 0) @@ -144,13 +144,13 @@ class GuiAutoImport (threading.Thread): gobject.timeout_add(1000, self.reset_startbutton) return True return False - + def reset_startbutton(self): if self.pipe_to_hud is not None: self.startButton.set_label(u' _Stop Autoimport ') else: self.startButton.set_label(u' _Start Autoimport ') - + return False @@ -169,7 +169,7 @@ class GuiAutoImport (threading.Thread): if widget.get_active(): # toggled on # - Does the lock acquisition need to be more sophisticated for multiple dirs? # (see comment above about what to do if pipe already open) - # - Ideally we want to release the lock if the auto-import is killed by some + # - Ideally we want to release the lock if the auto-import is killed by some # kind of exception - is this possible? if self.settings['global_lock'].acquire(False): # returns false immediately if lock not acquired print "\nGlobal lock taken ..." @@ -183,7 +183,7 @@ class GuiAutoImport (threading.Thread): command = os.path.join(sys.path[0], 'HUD_main.py') command = [command, ] + string.split(self.settings['cl_options']) bs = 1 - + try: self.pipe_to_hud = subprocess.Popen(command, bufsize=bs, stdin=subprocess.PIPE, @@ -191,17 +191,17 @@ class GuiAutoImport (threading.Thread): except: err = traceback.extract_tb(sys.exc_info()[2])[-1] print "*** GuiAutoImport Error opening pipe: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) - else: + else: for site in self.input_settings: self.importer.addImportDirectory(self.input_settings[site][0], True, site, self.input_settings[site][1]) print " * Add", site, " import directory", str(self.input_settings[site][0]) print "+Import directory - Site: " + site + " dir: " + str(self.input_settings[site][0]) - self.do_import() + self.do_import() interval = int(self.intervalEntry.get_text()) if self.importtimer != 0: gobject.source_remove(self.importtimer) self.importtimer = gobject.timeout_add(interval * 1000, self.do_import) - + else: print "auto-import aborted - global lock not available" else: # toggled off @@ -258,7 +258,7 @@ class GuiAutoImport (threading.Thread): vbox1.pack_start(pathHBox1, False, True, 0) pathHBox2 = gtk.HBox(False, 0) vbox2.pack_start(pathHBox2, False, True, 0) - + params = self.config.get_site_parameters(site) paths = self.config.get_default_paths(site) self.createSiteLine(pathHBox1, pathHBox2, site, False, paths['hud-defaultPath'], params['converter'], params['enabled']) @@ -281,7 +281,7 @@ if __name__== "__main__": parser.add_option("-q", "--quiet", action="store_false", dest="gui", default=True, help="don't start gui") parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int", help="How often to print a one-line status report (0 (default) means never)") - (options, sys.argv) = parser.parse_args() + (options, argv) = parser.parse_args() config = Configuration.Config() # db = fpdb_db.fpdb_db() @@ -305,4 +305,3 @@ if __name__== "__main__": gtk.main() else: pass - diff --git a/pyfpdb/GuiBulkImport.py b/pyfpdb/GuiBulkImport.py index ec262b08..7db420c7 100755 --- a/pyfpdb/GuiBulkImport.py +++ b/pyfpdb/GuiBulkImport.py @@ -43,7 +43,7 @@ class GuiBulkImport(): def dopulse(self): self.progressbar.pulse() return True - + def load_clicked(self, widget, data=None): stored = None dups = None @@ -58,9 +58,9 @@ class GuiBulkImport(): self.progressbar.set_text("Importing...") self.progressbar.pulse() while gtk.events_pending(): # see http://faq.pygtk.org/index.py?req=index for more hints (3.7) - gtk.main_iteration(False) + gtk.main_iteration(False) self.timer = gobject.timeout_add(100, self.dopulse) - + # get the dir to import from the chooser selected = self.chooser.get_filenames() @@ -87,7 +87,7 @@ class GuiBulkImport(): else: self.importer.setDropHudCache("auto") sitename = self.cbfilter.get_model()[self.cbfilter.get_active()][0] - + for selection in selected: self.importer.addBulkImportImportFileOrDir(selection, site = sitename) self.importer.setCallHud(False) @@ -99,7 +99,7 @@ class GuiBulkImport(): # raise Exceptions.FpdbError # finally: gobject.source_remove(self.timer) - + ttime = time() - starttime if ttime == 0: ttime = 1 @@ -324,9 +324,9 @@ def main(argv=None): help="If this option is passed it quits when it encounters any error") parser.add_option("-m", "--minPrint", "--status", dest="minPrint", default="0", type="int", help="How often to print a one-line status report (0 (default) means never)") - parser.add_option("-u", "--usage", action="store_true", dest="usage", default=False, + parser.add_option("-u", "--usage", action="store_true", dest="usage", default=False, help="Print some useful one liners") - (options, sys.argv) = parser.parse_args(args = argv) + (options, argv) = parser.parse_args(args = argv) if options.usage == True: #Print usage examples and exit @@ -339,7 +339,7 @@ def main(argv=None): sys.exit(0) config = Configuration.Config() - + settings = {} settings['minPrint'] = options.minPrint if os.name == 'nt': settings['os'] = 'windows' @@ -362,7 +362,7 @@ def main(argv=None): gtk.main() else: #Do something useful - importer = fpdb_import.Importer(False,settings, config) + importer = fpdb_import.Importer(False,settings, config) # importer.setDropIndexes("auto") importer.setDropIndexes("don't drop") importer.setFailOnError(options.failOnError) @@ -377,4 +377,3 @@ def main(argv=None): if __name__ == '__main__': sys.exit(main()) - diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py index fadce2fd..35709283 100644 --- a/pyfpdb/Options.py +++ b/pyfpdb/Options.py @@ -39,7 +39,7 @@ def fpdb_options(): return (options, argv) if __name__== "__main__": - (options, sys.argv) = fpdb_options() + (options, argv) = fpdb_options() print "errorsToConsole =", options.errorsToConsole print "database name =", options.dbname print "config file =", options.config diff --git a/pyfpdb/TournamentTracker.py b/pyfpdb/TournamentTracker.py index fd63d11c..72be8494 100644 --- a/pyfpdb/TournamentTracker.py +++ b/pyfpdb/TournamentTracker.py @@ -3,17 +3,17 @@ Based on HUD_main .. who knows if we want to actually use this or not """ # Copyright 2008, 2009, Eric Blade -# +# # 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 @@ -31,7 +31,7 @@ import os import Options import traceback -(options, sys.argv) = Options.fpdb_options() +(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_." @@ -55,7 +55,7 @@ import SummaryEverleaf class Tournament: """Tournament will hold the information about a tournament, I guess ? Remember I'm new to this language, so I don't know the best ways to do things""" - + def __init__(self, parent, site, tid): # site should probably be something in the config object, but i don't know how the config object works right now, so we're going to make it a str .. print "Tournament init" self.parent = parent @@ -74,7 +74,7 @@ class Tournament: self.prizepool = 0 self.players = {} # eventually i'd guess we'd probably want to fill this with playername:playerid's self.results = {} # i'd guess we'd want to load this up with playerid's instead of playernames, too, as well, also - + # if site == "Everleaf": # this should be attached to a button that says "retrieve tournament info" or something for sites that we know how to do it for summary = SummaryEverleaf.EverleafSummary() self.site = summary.parser.SiteName @@ -87,9 +87,9 @@ class Tournament: self.rebuys = (summary.parser.TourneyRebuys == "yes") self.prizepool = summary.parser.TourneyPool self.numplayers = summary.parser.TourneyPlayers - + self.openwindow() # let's start by getting any info we need.. meh - + def openwindow(self, widget=None): if self.window is not None: self.window.show() # isn't there a better way to bring something to the front? not that GTK focus works right anyway, ever @@ -102,24 +102,24 @@ class Tournament: self.window.set_border_width(1) self.window.set_default_size(480,640) self.window.set_resizable(True) - + self.main_vbox = gtk.VBox(False, 1) self.main_vbox.set_border_width(1) self.window.add(self.main_vbox) self.window.show() - + def addrebuy(self, widget=None): t = self t.numrebuys += 1 t.mylabel.set_label("%s - %s - %s - %s - %s %s - %s - %s - %s - %s - %s" % (t.site, t.id, t.starttime, t.endtime, t.structure, t.game, t.buyin, t.fee, t.numrebuys, t.numplayers, t.prizepool)) - + def delete_event(self, widget, event, data=None): return False - + def destroy(self, widget, data=None): return False - #end def destroy - + #end def destroy + class ttracker_main(object): """A main() object to own both the read_stdin thread and the gui.""" @@ -143,11 +143,11 @@ class ttracker_main(object): self.addbutton = gtk.Button(label="Enter Tournament") self.addbutton.connect("clicked", self.addClicked, "add tournament") self.vb.add(self.addbutton) - + self.main_window.add(self.vb) self.main_window.set_title("FPDB Tournament Tracker") self.main_window.show_all() - + def addClicked(self, widget, data): # what is "data"? i'm guessing anything i pass in after the function name in connect() but unsure because the documentation sucks print "addClicked", widget, data t = Tournament(self, None, None) @@ -162,7 +162,7 @@ class ttracker_main(object): rebuybutton = gtk.Button(label="Rebuy") rebuybutton.connect("clicked", t.addrebuy) self.vb.add(rebuybutton) - self.vb.add(editbutton) # These should probably be put in.. a.. h-box? i don't know.. + self.vb.add(editbutton) # These should probably be put in.. a.. h-box? i don't know.. self.vb.add(mylabel) self.main_window.resize_children() self.main_window.show() @@ -172,29 +172,29 @@ class ttracker_main(object): t.mylabel = mylabel t.editbutton = editbutton t.rebuybutton = rebuybutton - self.vb.show() + self.vb.show() print self.tourney_list - + return True else: return False # when we move the start command over to the main program, we can have the main program ask for the tourney id, and pipe it into the stdin here # at least that was my initial thought on it - + def destroy(*args): # call back for terminating the main eventloop gtk.main_quit() def create_HUD(self, new_hand_id, table, table_name, max, poker_game, stat_dict, cards): - + def idle_func(): - + gtk.gdk.threads_enter() try: newlabel = gtk.Label("%s - %s" % (table.site, table_name)) self.vb.add(newlabel) newlabel.show() self.main_window.resize_children() - + self.hud_dict[table_name].tablehudlabel = newlabel self.hud_dict[table_name].create(new_hand_id, self.config, stat_dict, cards) for m in self.hud_dict[table_name].aux_windows: @@ -212,11 +212,11 @@ class ttracker_main(object): self.hud_dict[table_name].cards = cards [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table_name].aux_windows] gobject.idle_add(idle_func) - + def update_HUD(self, new_hand_id, table_name, config): """Update a HUD gui from inside the non-gui read_stdin thread.""" # This is written so that only 1 thread can touch the gui--mainly -# for compatibility with Windows. This method dispatches the +# for compatibility with Windows. This method dispatches the # function idle_func() to be run by the gui thread, at its leisure. def idle_func(): gtk.gdk.threads_enter() @@ -227,7 +227,7 @@ class ttracker_main(object): finally: gtk.gdk.threads_leave() gobject.idle_add(idle_func) - + def read_stdin(self): # This is the thread function """Do all the non-gui heavy lifting for the HUD program.""" @@ -238,7 +238,7 @@ class ttracker_main(object): self.db_connection = Database.Database(self.config, self.db_name, 'temp') # self.db_connection.init_hud_stat_vars(hud_days) tourny_finder = re.compile('(\d+) (\d+)') - + while 1: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() new_hand_id = string.rstrip(new_hand_id) @@ -272,7 +272,7 @@ class ttracker_main(object): print "could not find tournament: skipping " sys.stderr.write("Could not find tournament %d in hand %d. Skipping.\n" % (int(tour_number), int(new_hand_id))) continue - + else: temp_key = table_name @@ -282,7 +282,7 @@ class ttracker_main(object): self.hud_dict[temp_key].cards = cards [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows] self.update_HUD(new_hand_id, temp_key, self.config) - + # Or create a new HUD else: if type == "tour": From ef9abd29480ccb32ca70410d410e477d373e3aac Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 11:21:55 +0800 Subject: [PATCH 06/14] Force all output from HHCs to by utf8 --- pyfpdb/HandHistoryConverter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/HandHistoryConverter.py b/pyfpdb/HandHistoryConverter.py index a662ca79..804b3534 100644 --- a/pyfpdb/HandHistoryConverter.py +++ b/pyfpdb/HandHistoryConverter.py @@ -94,7 +94,7 @@ follow : whether to tail -f the input""" else: log.info("Created directory '%s'" % out_dir) try: - self.out_fh = codecs.open(self.out_path, 'w', 'cp1252') + self.out_fh = codecs.open(self.out_path, 'w', 'utf8') except: log.error("out_path %s couldn't be opened" % (self.out_path)) else: From d617f1c4e005ce2dde00fd0c2796a642d7746537 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 16:39:59 +0800 Subject: [PATCH 07/14] [NEWIMPORT] Update Database.storeHandsPlayers --- pyfpdb/Database.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index dbd7afe1..1dddafb2 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1343,7 +1343,9 @@ class Database: q = q.replace('%s', self.sql.query['placeholder']) - self.cursor.execute(q, ( + c = self.connection.cursor() + + c.execute(q, ( p['tableName'], p['gameTypeId'], p['siteHandNo'], @@ -1374,7 +1376,7 @@ class Database: p['street4Pot'], p['showdownPot'] )) - return self.get_last_insert_id(self.cursor) + return self.get_last_insert_id(c) # def storeHand def storeHandsPlayers(self, hid, pids, pdata): @@ -1393,6 +1395,7 @@ class Database: pdata[p]['card6'], pdata[p]['card7'], pdata[p]['winnings'], + pdata[p]['rake'], pdata[p]['street0VPI'], pdata[p]['street1Seen'], pdata[p]['street2Seen'], @@ -1418,6 +1421,7 @@ class Database: card6, card7, winnings, + rake, street0VPI, street1Seen, street2Seen, @@ -1430,7 +1434,7 @@ class Database: street4Aggr ) VALUES ( - %s, %s, + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, @@ -1440,11 +1444,11 @@ class Database: # position, # tourneyTypeId, # startCards, -# rake, # totalProfit, # street0_3BChance, # street0_3BDone, # sawShowdown, +# wonAtSD, # otherRaisedStreet1, # otherRaisedStreet2, # otherRaisedStreet3, @@ -1454,7 +1458,6 @@ class Database: # foldToOtherRaisedStreet3, # foldToOtherRaisedStreet4, # wonWhenSeenStreet1, -# wonAtSD, # stealAttemptChance, # stealAttempted, # foldBbToStealChance, @@ -1499,7 +1502,9 @@ class Database: q = q.replace('%s', self.sql.query['placeholder']) #print "DEBUG: inserts: %s" %inserts - self.cursor.executemany(q, inserts) + #print "DEBUG: q: %s" % q + c = self.connection.cursor() + c.executemany(q, inserts) def storeHudCacheNew(self, gid, pid, hc): q = """INSERT INTO HudCache ( From d65ee55b64f23e03aabe124208d6d12c08ad719c Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 16:44:46 +0800 Subject: [PATCH 08/14] [NEWIMPORT] Calculate rake, make card calculation holdem only Stud card fetching still terminally broken --- pyfpdb/DerivedStats.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index e4d59336..2d9869ea 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -39,6 +39,7 @@ class DerivedStats(): #Init vars that may not be used, but still need to be inserted. # All stud street4 need this when importing holdem self.handsplayers[player[1]]['winnings'] = 0 + self.handsplayers[player[1]]['rake'] = 0 self.handsplayers[player[1]]['street4Seen'] = False self.handsplayers[player[1]]['street4Aggr'] = False @@ -109,6 +110,11 @@ class DerivedStats(): # rake taken out. hand.collectees is Decimal, database requires cents for player in hand.collectees: self.handsplayers[player]['winnings'] = int(100 * hand.collectees[player]) + #FIXME: This is pretty dodgy, rake = hand.rake/#collectees + # You can really only pay rake when you collect money, but + # different sites calculate rake differently. + # Should be fine for split-pots, but won't be accurate for multi-way pots + self.handsplayers[player]['rake'] = int(100* hand.rake)/len(hand.collectees) for i, street in enumerate(hand.actionStreets[2:]): self.seen(self.hand, i+1) @@ -116,12 +122,20 @@ class DerivedStats(): for i, street in enumerate(hand.actionStreets[1:]): self.aggr(self.hand, i) - default_holecards = ["Xx", "Xx", "Xx", "Xx"] + #default_holecards = ["Xx", "Xx", "Xx", "Xx"] + #if hand.gametype['base'] == "hold": + # pass + #elif hand.gametype['base'] == "stud": + # pass + #else: + # # Flop hopefully... + # pass for street in hand.holeStreets: for player in hand.players: for i in range(1,8): self.handsplayers[player[1]]['card%d' % i] = 0 - if player[1] in hand.holecards[street].keys(): + #print "DEBUG: hand.holecards[%s]: %s" % (street, hand.holecards[street]) + if player[1] in hand.holecards[street].keys() and hand.gametype['base'] == "hold": self.handsplayers[player[1]]['card1'] = Card.encodeCard(hand.holecards[street][player[1]][1][0]) self.handsplayers[player[1]]['card2'] = Card.encodeCard(hand.holecards[street][player[1]][1][1]) try: From 48c395565ece9f8151c5f6c5621d6608c9386780 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 17:29:54 +0800 Subject: [PATCH 09/14] [NEWIMPORT] Add HandsPlayers.totalProfit Grapher now works after import yay --- pyfpdb/Database.py | 5 +++-- pyfpdb/DerivedStats.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 1dddafb2..9134fc6a 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1396,6 +1396,7 @@ class Database: pdata[p]['card7'], pdata[p]['winnings'], pdata[p]['rake'], + pdata[p]['totalProfit'], pdata[p]['street0VPI'], pdata[p]['street1Seen'], pdata[p]['street2Seen'], @@ -1422,6 +1423,7 @@ class Database: card7, winnings, rake, + totalProfit, street0VPI, street1Seen, street2Seen, @@ -1434,7 +1436,7 @@ class Database: street4Aggr ) VALUES ( - %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, @@ -1444,7 +1446,6 @@ class Database: # position, # tourneyTypeId, # startCards, -# totalProfit, # street0_3BChance, # street0_3BDone, # sawShowdown, diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 2d9869ea..1cb39a07 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -40,6 +40,7 @@ class DerivedStats(): # All stud street4 need this when importing holdem self.handsplayers[player[1]]['winnings'] = 0 self.handsplayers[player[1]]['rake'] = 0 + self.handsplayers[player[1]]['totalProfit'] = 0 self.handsplayers[player[1]]['street4Seen'] = False self.handsplayers[player[1]]['street4Aggr'] = False @@ -96,9 +97,6 @@ class DerivedStats(): #print "DEBUG: playersAtStreet 1:'%s' 2:'%s' 3:'%s' 4:'%s'" %(self.hands['playersAtStreet1'],self.hands['playersAtStreet2'],self.hands['playersAtStreet3'],self.hands['playersAtStreet4']) self.streetXRaises(hand) # Empty function currently - # comment TEXT, - # commentTs DATETIME - def assembleHandsPlayers(self, hand): #street0VPI/vpip already called in Hand #hand.players = [[seat, name, chips],[seat, name, chips]] @@ -116,6 +114,9 @@ class DerivedStats(): # Should be fine for split-pots, but won't be accurate for multi-way pots self.handsplayers[player]['rake'] = int(100* hand.rake)/len(hand.collectees) + for player in hand.pot.committed: + self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) + for i, street in enumerate(hand.actionStreets[2:]): self.seen(self.hand, i+1) From e9a858e4cdc04bc2469c2e688a367d1de8f50c5a Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 17:55:00 +0800 Subject: [PATCH 10/14] Make DEBUG = False in DerivedStats --- pyfpdb/DerivedStats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 1cb39a07..4366d067 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -18,7 +18,7 @@ #fpdb modules import Card -DEBUG = True +DEBUG = False if DEBUG: import pprint From dc1b0d2e38df78a5e5ee0ddae8c4863525ab3ddc Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 21:58:27 +0800 Subject: [PATCH 11/14] [NEWIMPORT] Calculate wonWhenSeenStreet1 Won is defined as 'collected money from the pot' Seen street 1 does not require vpip on street 0 --- pyfpdb/Database.py | 9 +++++---- pyfpdb/DerivedStats.py | 14 +++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 9134fc6a..ed987d81 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1406,7 +1406,8 @@ class Database: pdata[p]['street1Aggr'], pdata[p]['street2Aggr'], pdata[p]['street3Aggr'], - pdata[p]['street4Aggr'] + pdata[p]['street4Aggr'], + pdata[p]['wonWhenSeenStreet1'] ) ) q = """INSERT INTO HandsPlayers ( @@ -1433,10 +1434,11 @@ class Database: street1Aggr, street2Aggr, street3Aggr, - street4Aggr + street4Aggr, + wonWhenSeenStreet1 ) 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, @@ -1458,7 +1460,6 @@ class Database: # foldToOtherRaisedStreet2, # foldToOtherRaisedStreet3, # foldToOtherRaisedStreet4, -# wonWhenSeenStreet1, # stealAttemptChance, # stealAttempted, # foldBbToStealChance, diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 4366d067..c6b3fc64 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -43,6 +43,7 @@ class DerivedStats(): self.handsplayers[player[1]]['totalProfit'] = 0 self.handsplayers[player[1]]['street4Seen'] = False self.handsplayers[player[1]]['street4Aggr'] = False + self.handsplayers[player[1]]['wonWhenSeenStreet1'] = False self.assembleHands(self.hand) self.assembleHandsPlayers(self.hand) @@ -104,6 +105,12 @@ class DerivedStats(): self.handsplayers[player[1]]['seatNo'] = player[0] self.handsplayers[player[1]]['startCash'] = player[2] + for i, street in enumerate(hand.actionStreets[2:]): + self.seen(self.hand, i+1) + + for i, street in enumerate(hand.actionStreets[1:]): + self.aggr(self.hand, i) + # Winnings is a non-negative value of money collected from the pot, which already includes the # rake taken out. hand.collectees is Decimal, database requires cents for player in hand.collectees: @@ -113,15 +120,12 @@ class DerivedStats(): # different sites calculate rake differently. # Should be fine for split-pots, but won't be accurate for multi-way pots self.handsplayers[player]['rake'] = int(100* hand.rake)/len(hand.collectees) + if self.handsplayers[player]['street1Seen'] == True: + self.handsplayers[player]['wonWhenSeenStreet1'] = True for player in hand.pot.committed: self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) - for i, street in enumerate(hand.actionStreets[2:]): - self.seen(self.hand, i+1) - - for i, street in enumerate(hand.actionStreets[1:]): - self.aggr(self.hand, i) #default_holecards = ["Xx", "Xx", "Xx", "Xx"] #if hand.gametype['base'] == "hold": From 9203e0b0d8418cd33000662c2beab9520d754cce Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 23:14:43 +0800 Subject: [PATCH 12/14] [NEWIMPORT] HandsPlayers.sawShowdown added --- pyfpdb/Database.py | 4 +++- pyfpdb/DerivedStats.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index ed987d81..42fee82b 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1402,6 +1402,7 @@ class Database: pdata[p]['street2Seen'], pdata[p]['street3Seen'], pdata[p]['street4Seen'], + pdata[p]['sawShowdown'], pdata[p]['street0Aggr'], pdata[p]['street1Aggr'], pdata[p]['street2Aggr'], @@ -1430,6 +1431,7 @@ class Database: street2Seen, street3Seen, street4Seen, + sawShowdown, street0Aggr, street1Aggr, street2Aggr, @@ -1438,6 +1440,7 @@ class Database: wonWhenSeenStreet1 ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, @@ -1450,7 +1453,6 @@ class Database: # startCards, # street0_3BChance, # street0_3BDone, -# sawShowdown, # wonAtSD, # otherRaisedStreet1, # otherRaisedStreet2, diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index c6b3fc64..3214cbbb 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -44,6 +44,7 @@ class DerivedStats(): self.handsplayers[player[1]]['street4Seen'] = False self.handsplayers[player[1]]['street4Aggr'] = False self.handsplayers[player[1]]['wonWhenSeenStreet1'] = False + self.handsplayers[player[1]]['sawShowdown'] = False self.assembleHands(self.hand) self.assembleHandsPlayers(self.hand) @@ -100,6 +101,8 @@ class DerivedStats(): def assembleHandsPlayers(self, hand): #street0VPI/vpip already called in Hand + # sawShowdown is calculated in playersAtStreetX, as that calculation gives us a convenient list of names + #hand.players = [[seat, name, chips],[seat, name, chips]] for player in hand.players: self.handsplayers[player[1]]['seatNo'] = player[0] @@ -193,7 +196,11 @@ class DerivedStats(): self.hands['playersAtStreet%d' % (i+1)] = len(set.union(alliners, actors)) actions = hand.actions[hand.actionStreets[-1]] - self.hands['playersAtShowdown'] = len(set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners)) + pas = set.union(self.pfba(actions) - self.pfba(actions, l=('folds',)), alliners) + self.hands['playersAtShowdown'] = len(pas) + + for player in pas: + self.handsplayers[player]['sawShowdown'] = True def streetXRaises(self, hand): # self.actions[street] is a list of all actions in a tuple, contining the action as the second element From 3bb1656603759513185e9e8f7e4d49fb842a7c65 Mon Sep 17 00:00:00 2001 From: Worros Date: Tue, 24 Nov 2009 23:23:21 +0800 Subject: [PATCH 13/14] [NEWIMPORT] Add HandsPlayers.wonAtSD --- pyfpdb/Database.py | 5 +++-- pyfpdb/DerivedStats.py | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 42fee82b..9bfe9dd6 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1403,6 +1403,7 @@ class Database: pdata[p]['street3Seen'], pdata[p]['street4Seen'], pdata[p]['sawShowdown'], + pdata[p]['wonAtSD'], pdata[p]['street0Aggr'], pdata[p]['street1Aggr'], pdata[p]['street2Aggr'], @@ -1432,6 +1433,7 @@ class Database: street3Seen, street4Seen, sawShowdown, + wonAtSD, street0Aggr, street1Aggr, street2Aggr, @@ -1440,7 +1442,7 @@ class Database: wonWhenSeenStreet1 ) VALUES ( - %s, + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, @@ -1453,7 +1455,6 @@ class Database: # startCards, # street0_3BChance, # street0_3BDone, -# wonAtSD, # otherRaisedStreet1, # otherRaisedStreet2, # otherRaisedStreet3, diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index 3214cbbb..fabadb3b 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -45,6 +45,7 @@ class DerivedStats(): self.handsplayers[player[1]]['street4Aggr'] = False self.handsplayers[player[1]]['wonWhenSeenStreet1'] = False self.handsplayers[player[1]]['sawShowdown'] = False + self.handsplayers[player[1]]['wonAtSD'] = False self.assembleHands(self.hand) self.assembleHandsPlayers(self.hand) @@ -125,6 +126,8 @@ class DerivedStats(): self.handsplayers[player]['rake'] = int(100* hand.rake)/len(hand.collectees) if self.handsplayers[player]['street1Seen'] == True: self.handsplayers[player]['wonWhenSeenStreet1'] = True + if self.handsplayers[player]['sawShowdown'] == True: + self.handsplayers[player]['wonAtSD'] = True for player in hand.pot.committed: self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) From a87a145c28031d517b74b98162129619dffe1432 Mon Sep 17 00:00:00 2001 From: Worros Date: Wed, 25 Nov 2009 00:57:14 +0800 Subject: [PATCH 14/14] [NEWIMPORT] HandsPlayers.streetXCalls and streetXBets Pretty sure the bets stat is wrong --- pyfpdb/Database.py | 36 ++++++++++++++++++++++++------------ pyfpdb/DerivedStats.py | 20 +++++++++++++++++++- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 9bfe9dd6..9f214aeb 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -1409,7 +1409,17 @@ class Database: pdata[p]['street2Aggr'], pdata[p]['street3Aggr'], pdata[p]['street4Aggr'], - pdata[p]['wonWhenSeenStreet1'] + pdata[p]['wonWhenSeenStreet1'], + pdata[p]['street0Calls'], + pdata[p]['street1Calls'], + pdata[p]['street2Calls'], + pdata[p]['street3Calls'], + pdata[p]['street4Calls'], + pdata[p]['street0Bets'], + pdata[p]['street1Bets'], + pdata[p]['street2Bets'], + pdata[p]['street3Bets'], + pdata[p]['street4Bets'], ) ) q = """INSERT INTO HandsPlayers ( @@ -1439,7 +1449,17 @@ class Database: street2Aggr, street3Aggr, street4Aggr, - wonWhenSeenStreet1 + wonWhenSeenStreet1, + street0Calls, + street1Calls, + street2Calls, + street3Calls, + street4Calls, + street0Bets, + street1Bets, + street2Bets, + street3Bets, + street4Bets ) VALUES ( %s, %s, @@ -1447,6 +1467,8 @@ class Database: %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 )""" @@ -1493,16 +1515,6 @@ class Database: # street3CheckCallRaiseDone, # street4CheckCallRaiseChance, # street4CheckCallRaiseDone, -# street0Calls, -# street1Calls, -# street2Calls, -# street3Calls, -# street4Calls, -# street0Bets, -# street1Bets, -# street2Bets, -# street3Bets, -# street4Bets q = q.replace('%s', self.sql.query['placeholder']) diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index fabadb3b..3da670cc 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -46,6 +46,9 @@ class DerivedStats(): self.handsplayers[player[1]]['wonWhenSeenStreet1'] = False self.handsplayers[player[1]]['sawShowdown'] = False self.handsplayers[player[1]]['wonAtSD'] = False + for i in range(5): + self.handsplayers[player[1]]['street%dCalls' % i] = 0 + self.handsplayers[player[1]]['street%dBets' % i] = 0 self.assembleHands(self.hand) self.assembleHandsPlayers(self.hand) @@ -114,6 +117,8 @@ class DerivedStats(): for i, street in enumerate(hand.actionStreets[1:]): self.aggr(self.hand, i) + self.calls(self.hand, i) + self.bets(self.hand, i) # Winnings is a non-negative value of money collected from the pot, which already includes the # rake taken out. hand.collectees is Decimal, database requires cents @@ -132,7 +137,6 @@ class DerivedStats(): for player in hand.pot.committed: self.handsplayers[player]['totalProfit'] = int(self.handsplayers[player]['winnings'] - (100*hand.pot.committed[player])) - #default_holecards = ["Xx", "Xx", "Xx", "Xx"] #if hand.gametype['base'] == "hold": # pass @@ -240,6 +244,20 @@ class DerivedStats(): else: self.handsplayers[player[1]]['street%sAggr' % i] = False + def calls(self, hand, i): + callers = [] + for act in hand.actions[hand.actionStreets[i+1]]: + if act[1] in ('calls'): + self.handsplayers[act[0]]['street%sCalls' % i] = 1 + self.handsplayers[act[0]]['street%sCalls' % i] + + # CG - I'm sure this stat is wrong + # Best guess is that raise = 2 bets + def bets(self, hand, i): + betters = [] + for act in hand.actions[hand.actionStreets[i+1]]: + if act[1] in ('bets'): + self.handsplayers[act[0]]['street%sBets' % i] = 1 + self.handsplayers[act[0]]['street%sBets' % i] + def countPlayers(self, hand): pass