diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py index f89c318a..e9f1521c 100755 --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -82,6 +82,11 @@ def get_exec_path(): def get_config(file_name, fallback = True): """Looks in cwd and in self.default_config_path for a config file.""" + + # look for example file even if not used here, path is returned to caller + config_found,example_found,example_copy = False,False,False + config_path, example_path = None,None + exec_dir = get_exec_path() if file_name == 'logging.conf' and not hasattr(sys, "frozen"): config_path = os.path.join(exec_dir, 'pyfpdb', file_name) @@ -89,17 +94,13 @@ def get_config(file_name, fallback = True): config_path = os.path.join(exec_dir, file_name) # print "config_path=", config_path if os.path.exists(config_path): # there is a file in the cwd - return (config_path,False) # so we use it + config_found = True # so we use it else: # no file in the cwd, look where it should be in the first place default_dir = get_default_config_path() config_path = os.path.join(default_dir, file_name) # print "config path 2=", config_path if os.path.exists(config_path): - return (config_path,False) - -# No file found - if not fallback: - return (False,False) + config_found = True # Example configuration for debian package if os.name == 'posix': @@ -108,38 +109,43 @@ def get_config(file_name, fallback = True): # the config directory for us so there's no need to check it # again example_path = '/usr/share/python-fpdb/' + file_name + '.example' - try: - shutil.copyfile(example_path, config_path) - msg = _("Config file has been created at %s.\n") % config_path - logging.info(msg) - return (config_path,False) - except IOError: - pass + if not config_found and fallback: + try: + shutil.copyfile(example_path, config_path) + example_copy = True + msg = _("Config file has been created at %s.\n") % config_path + logging.info(msg) + except IOError: + pass # OK, fall back to the .example file, should be in the start dir - if os.path.exists(file_name + ".example"): + elif os.path.exists(file_name + ".example"): try: print "" + example_path = file_name + ".example" check_dir(default_dir) - shutil.copyfile(file_name + ".example", config_path) - msg = _("No %s found\n in %s\n or %s\n") % (file_name, exec_dir, default_dir) \ - + _("Config file has been created at %s.\n") % config_path - print msg - logging.info(msg) - file_name = config_path + if not config_found and fallback: + shutil.copyfile(example_path, config_path) + example_copy = True + msg = _("No %s found\n in %s\n or %s\n") % (file_name, exec_dir, default_dir) \ + + _("Config file has been created at %s.\n") % config_path + print msg + logging.info(msg) except: print _("Error copying .example file, cannot fall back. Exiting.\n") sys.stderr.write(_("Error copying .example file, cannot fall back. Exiting.\n")) sys.stderr.write( str(sys.exc_info()) ) sys.exit() - else: + elif fallback: print _("No %s found, cannot fall back. Exiting.\n") % file_name sys.stderr.write(_("No %s found, cannot fall back. Exiting.\n") % file_name) sys.exit() - return (file_name,True) + + #print "get_config: returning "+str( (config_path,example_copy,example_path) ) + return (config_path,example_copy,example_path) def get_logger(file_name, config = "config", fallback = False, log_dir=None, log_file=None): - (conf_file,copied) = get_config(file_name, fallback = fallback) + (conf_file,copied,example_file) = get_config(file_name, fallback = fallback) if log_dir is None: log_dir = os.path.join(get_exec_path(), u'log') @@ -674,7 +680,7 @@ class Config: sys.stderr.write(_("Configuration file %s not found. Using defaults.") % (file)) file = None - if file is None: (file,self.example_copy) = get_config("HUD_config.xml", True) + if file is None: (file,self.example_copy,example_file) = get_config("HUD_config.xml", True) self.file = file self.dir_self = get_exec_path() @@ -685,27 +691,6 @@ class Config: self.log_file = os.path.join(self.dir_log, u'fpdb-log.txt') log = get_logger("logging.conf", "config", log_dir=self.dir_log) -# Parse even if there was no real config file found and we are using the example -# If using the example, we'll edit it later - log.info(_("Reading configuration file %s") % file) - print _("\nReading configuration file %s\n") % file - try: - doc = xml.dom.minidom.parse(file) - self.file_error = None - except: - log.error(_("Error parsing %s. See error log file.") % (file)) - traceback.print_exc(file=sys.stderr) - self.file_error = sys.exc_info()[1] - # we could add a parameter to decide whether to return or read a line and exit? - return - #print "press enter to continue" - #sys.stdin.readline() - #sys.exit() -#ExpatError: not well-formed (invalid token): line 511, column 4 -#sys.exc_info = (, ExpatError('not well-formed (invalid token): line 511, -# column 4',), ) - - self.doc = doc self.supported_sites = {} self.supported_games = {} self.supported_databases = {} # databaseName --> Database instance @@ -717,6 +702,32 @@ class Config: self.emails = {} self.gui_cash_stats = GUICashStats() + added,n = 1,0 # use n to prevent infinite loop if add_missing_elements() fails somehow + while added > 0 and n < 2: + n = n + 1 + log.info(_("Reading configuration file %s") % file) + print _("\nReading configuration file %s\n") % file + try: + doc = xml.dom.minidom.parse(file) + self.doc = doc + self.file_error = None + except: + log.error(_("Error parsing %s. See error log file.") % (file)) + traceback.print_exc(file=sys.stderr) + self.file_error = sys.exc_info()[1] + # we could add a parameter to decide whether to return or read a line and exit? + return + #print "press enter to continue" + #sys.stdin.readline() + #sys.exit() +#ExpatError: not well-formed (invalid token): line 511, column 4 +#sys.exc_info = (, ExpatError('not well-formed (invalid token): line 511, +# column 4',), ) + + if not self.example_copy and example_file is not None: + # reads example file and adds missing elements into current config + added = self.add_missing_elements(doc, example_file) + if doc.getElementsByTagName("general") == []: self.general.get_defaults() for gen_node in doc.getElementsByTagName("general"): @@ -749,6 +760,7 @@ class Config: raise ValueError("Database names must be unique") if self.db_selected is None or db.db_selected: self.db_selected = db.db_name + db_node.setAttribute("default", "True") 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 # ..when we parse the xml we allow for ''. there has to be a decission if to allow '' or not @@ -791,7 +803,7 @@ class Config: self.set_db_parameters(db_name = 'fpdb', db_ip = df_parms['db-host'], db_user = df_parms['db-user'], db_pass = df_parms['db-password']) - self.save(file=os.path.join(self.default_config_path, "HUD_config.xml")) + self.save(file=os.path.join(self.dir_config, "HUD_config.xml")) if doc.getElementsByTagName("raw_hands") == []: self.raw_hands = RawHands() @@ -806,6 +818,40 @@ class Config: print "" #end def __init__ + def add_missing_elements(self, doc, example_file): + """ Look through example config file and add any elements that are not in the config + May need to add some 'enabled' attributes to turn things off - can't just delete a + config section now because this will add it back in""" + + nodes_added = 0 + + try: + example_doc = xml.dom.minidom.parse(example_file) + except: + log.error(_("Error parsing example file %s. See error log file.") % (example_file)) + return nodes_added + + for cnode in doc.getElementsByTagName("FreePokerToolsConfig"): + for example_cnode in example_doc.childNodes: + if example_cnode.localName == "FreePokerToolsConfig": + for e in example_cnode.childNodes: + #print "nodetype", e.nodeType, "name", e.localName, "found", len(doc.getElementsByTagName(e.localName)) + if e.nodeType == e.ELEMENT_NODE and doc.getElementsByTagName(e.localName) == []: + new = doc.importNode(e, True) # True means do deep copy + t_node = self.doc.createTextNode(" ") + cnode.appendChild(t_node) + cnode.appendChild(new) + t_node = self.doc.createTextNode("\r\n\r\n") + cnode.appendChild(t_node) + print "... adding missing config section: " + e.localName + nodes_added = nodes_added + 1 + + if nodes_added > 0: + print "Added %d missing config sections\n" % nodes_added + self.save() + + return nodes_added + def set_hhArchiveBase(self, path): self.imp.node.setAttribute("hhArchiveBase", path) @@ -1039,7 +1085,11 @@ class Config: if db_user is not None: db_node.setAttribute("db_user", db_user) if db_pass is not None: db_node.setAttribute("db_pass", db_pass) if db_server is not None: db_node.setAttribute("db_server", db_server) - if defaultb: db_node.setAttribute("default", default) + if defaultb or self.db_selected == db_name: + db_node.setAttribute("default", "True") + for dbn in self.doc.getElementsByTagName("database"): + if dbn.getAttribute('db_name') != db_name and dbn.hasAttribute("default"): + dbn.removeAttribute("default") elif db_node.hasAttribute("default"): db_node.removeAttribute("default") if self.supported_databases.has_key(db_name): @@ -1052,6 +1102,64 @@ class Config: if defaultb: self.db_selected = db_name return + + def add_db_parameters(self, db_name = 'fpdb', db_ip = None, db_user = None, + db_pass = None, db_desc = None, db_server = None, + default = "False"): + default = default.lower() + defaultb = string_to_bool(default, False) + if db_name in self.supported_databases: + raise ValueError("Database names must be unique") + + db_node = self.get_db_node(db_name) + if db_node is None: + for db_node in self.doc.getElementsByTagName("supported_databases"): + # should only be one supported_databases element, use last one if there are several + suppdb_node = db_node + t_node = self.doc.createTextNode(" ") + suppdb_node.appendChild(t_node) + db_node = self.doc.createElement("database") + suppdb_node.appendChild(db_node) + t_node = self.doc.createTextNode("\r\n ") + suppdb_node.appendChild(t_node) + db_node.setAttribute("db_name", db_name) + if db_desc is not None: db_node.setAttribute("db_desc", db_desc) + if db_ip is not None: db_node.setAttribute("db_ip", db_ip) + if db_user is not None: db_node.setAttribute("db_user", db_user) + if db_pass is not None: db_node.setAttribute("db_pass", db_pass) + if db_server is not None: db_node.setAttribute("db_server", db_server) + if defaultb: + db_node.setAttribute("default", "True") + for dbn in self.doc.getElementsByTagName("database"): + if dbn.getAttribute('db_name') != db_name and dbn.hasAttribute("default"): + dbn.removeAttribute("default") + elif db_node.hasAttribute("default"): + db_node.removeAttribute("default") + else: + if db_desc is not None: db_node.setAttribute("db_desc", db_desc) + if db_ip is not None: db_node.setAttribute("db_ip", db_ip) + if db_user is not None: db_node.setAttribute("db_user", db_user) + if db_pass is not None: db_node.setAttribute("db_pass", db_pass) + if db_server is not None: db_node.setAttribute("db_server", db_server) + if defaultb or self.db_selected == db_name: + db_node.setAttribute("default", "True") + elif db_node.hasAttribute("default"): + db_node.removeAttribute("default") + + if self.supported_databases.has_key(db_name): + if db_desc is not None: self.supported_databases[db_name].dp_desc = db_desc + if db_ip is not None: self.supported_databases[db_name].dp_ip = db_ip + if db_user is not None: self.supported_databases[db_name].dp_user = db_user + if db_pass is not None: self.supported_databases[db_name].dp_pass = db_pass + if db_server is not None: self.supported_databases[db_name].dp_server = db_server + self.supported_databases[db_name].db_selected = defaultb + else: + db = Database(node=db_node) + self.supported_databases[db.db_name] = db + + if defaultb: + self.db_selected = db_name + return def get_backend(self, name): """Returns the number of the currently used backend""" diff --git a/pyfpdb/DerivedStats.py b/pyfpdb/DerivedStats.py index dde8ecca..d79d49bf 100644 --- a/pyfpdb/DerivedStats.py +++ b/pyfpdb/DerivedStats.py @@ -19,6 +19,10 @@ import Card from decimal import Decimal +import logging +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("parser") + DEBUG = False if DEBUG: diff --git a/pyfpdb/GuiDatabase.py b/pyfpdb/GuiDatabase.py index 0f7cdab1..599f0a7e 100755 --- a/pyfpdb/GuiDatabase.py +++ b/pyfpdb/GuiDatabase.py @@ -19,6 +19,7 @@ import os import sys import traceback import Queue +import re import pygtk pygtk.require('2.0') @@ -32,6 +33,7 @@ log = logging.getLogger("maintdbs") import Exceptions +import Configuration import Database import SQL @@ -79,8 +81,17 @@ class GuiDatabase: try: #self.dia.set_modal(True) self.vbox = self.dia.vbox + self.action_area = self.dia.action_area #gtk.Widget.set_size_request(self.vbox, 700, 400); + h = gtk.HBox(False, spacing=3) + h.show() + self.vbox.pack_start(h, padding=3) + + vbtn = gtk.VBox(True, spacing=3) + vbtn.show() + h.pack_start(vbtn, expand=False, fill=False, padding=2) + # list of databases in self.config.supported_databases: self.liststore = gtk.ListStore(str, str, str, str, str ,str, str, str, str, str) @@ -101,12 +112,15 @@ class GuiDatabase: self.scrolledwindow = gtk.ScrolledWindow() self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolledwindow.add(self.listview) - self.vbox.pack_start(self.scrolledwindow, expand=True, fill=True, padding=0) + h.pack_start(self.scrolledwindow, expand=True, fill=True, padding=0) - refreshbutton = gtk.Button(_("Refresh")) - refreshbutton.connect("clicked", self.refresh, None) - self.vbox.pack_start(refreshbutton, False, False, 3) - refreshbutton.show() + add_button = SideButton(_("_Add"), gtk.STOCK_ADD) + add_button.connect("clicked", self.addDB, None) + vbtn.pack_start(add_button, False, False, 3) + + refresh_button = SideButton(_("_Refresh"), gtk.STOCK_REFRESH) + refresh_button.connect("clicked", self.refresh, None) + vbtn.pack_start(refresh_button, False, False, 3) col = self.addTextColumn(_("Type"), 0, False) col = self.addTextColumn(_("Name"), 1, False) @@ -114,7 +128,7 @@ class GuiDatabase: col = self.addTextColumn(_("Username"), 3, True) col = self.addTextColumn(_("Password"), 4, True) col = self.addTextColumn(_("Host"), 5, True) - col = self.addTextObjColumn(_("Default"), 6, 6) + col = self.addTextObjColumn(_("Open"), 6, 6) col = self.addTextObjColumn(_("Status"), 7, 8) #self.listview.get_selection().set_mode(gtk.SELECTION_SINGLE) @@ -122,6 +136,7 @@ class GuiDatabase: self.listview.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.listview.connect('button_press_event', self.selectTest) + self.dia.show_all() self.loadDbs() #self.dia.connect('response', self.dialog_response_cb) @@ -248,9 +263,9 @@ class GuiDatabase: self.liststore.clear() #self.listcols = [] - dia = self.info_box2(None, _('Testing database connections ... '), "", False, False) + dia = InfoBox( parent=self.dia, str1=_('Testing database connections ... ') ) while gtk.events_pending(): - gtk.mainiteration() + gtk.main_iteration() try: # want to fill: dbms, name, comment, user, passwd, host, default, status, icon @@ -268,57 +283,14 @@ class GuiDatabase: default = (name == self.config.db_selected) default_icon = None if default: default_icon = gtk.STOCK_APPLY - status = "" - icon = None - err_msg = "" - sql = SQL.Sql(db_server=dbms) - db = Database.Database(self.config, sql = sql, autoconnect = False) - # try to connect to db, set status and err_msg if it fails - try: - # is creating empty db for sqlite ... mod db.py further? - # add noDbTables flag to db.py? - log.debug(_("loaddbs: trying to connect to: %s/%s, %s, %s/%s") % (str(dbms_num),dbms,name,user,passwd)) - db.connect(backend=dbms_num, host=host, database=name, user=user, password=passwd, create=False) - if db.connected: - log.debug(_(" connected ok")) - status = 'ok' - icon = gtk.STOCK_APPLY - if db.wrongDbVersion: - status = 'old' - icon = gtk.STOCK_INFO - else: - log.debug(_(" not connected but no exception")) - except Exceptions.FpdbMySQLAccessDenied: - err_msg = _("MySQL Server reports: Access denied. Are your permissions set correctly?") - status = "failed" - icon = gtk.STOCK_CANCEL - except Exceptions.FpdbMySQLNoDatabase: - err_msg = _("MySQL client reports: 2002 or 2003 error. Unable to connect - Please check that the MySQL service has been started") - status = "failed" - icon = gtk.STOCK_CANCEL - except Exceptions.FpdbPostgresqlAccessDenied: - err_msg = _("Postgres Server reports: Access denied. Are your permissions set correctly?") - status = "failed" - except Exceptions.FpdbPostgresqlNoDatabase: - err_msg = _("Postgres client reports: Unable to connect - Please check that the Postgres service has been started") - status = "failed" - icon = gtk.STOCK_CANCEL - except: - err = traceback.extract_tb(sys.exc_info()[2])[-1] - log.info( 'db connection to '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' - + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) )#TODO Gettextify - status = "failed" - icon = gtk.STOCK_CANCEL - if err_msg: - log.info( 'db connection to '+str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' - + err_msg )#TODO Gettextify + status, err_msg, icon = GuiDatabase.testDB(self.config, dbms, dbms_num, name, user, passwd, host) b = gtk.Button(name) b.show() iter = self.liststore.append( (dbms, name, comment, user, passwd, host, "", default_icon, status, icon) ) - self.info_box2(dia[0], _("finished."), "", False, True) + dia.add_msg( _("finished."), False, True ) self.listview.show() self.scrolledwindow.show() self.vbox.show() @@ -356,64 +328,368 @@ class GuiDatabase: def refresh(self, widget, data): self.loadDbs() - def info_box(self, dia, str1, str2, run, destroy): - if dia is None: - #if run: - btns = gtk.BUTTONS_NONE - btns = gtk.BUTTONS_OK - dia = gtk.MessageDialog( parent=self.main_window, flags=gtk.DIALOG_DESTROY_WITH_PARENT - , type=gtk.MESSAGE_INFO, buttons=(btns), message_format=str1 ) - # try to remove buttons! - # (main message is in inverse video if no buttons, so try removing them after - # creating dialog) - # NO! message just goes back to inverse video :-( use info_box2 instead - for c in dia.vbox.get_children(): - if isinstance(c, gtk.HButtonBox): - for d in c.get_children(): - log.info('child: '+str(d)+' is a '+str(d.__class__)) - if isinstance(d, gtk.Button): - log.info(_('removing button %s'% str(d))) - c.remove(d) - if str2: - dia.format_secondary_text(str2) - else: - dia.set_markup(str1) - if str2: - dia.format_secondary_text(str2) - dia.show() - response = None - if run: response = dia.run() - if destroy: dia.destroy() - return (dia, response) + def addDB(self, widget, data): + adb = AddDB(self.config, self.dia) + (status, err_msg, icon, dbms, dbms_num, name, comment, user, passwd, host) = adb.run() + adb.destroy() - def info_box2(self, dia, str1, str2, run, destroy): - if dia is None: - # create dialog and add icon and label - btns = (gtk.BUTTONS_OK) - btns = None - # messagedialog puts text in inverse colors if no buttons are displayed?? - #dia = gtk.MessageDialog( parent=self.main_window, flags=gtk.DIALOG_DESTROY_WITH_PARENT - # , type=gtk.MESSAGE_INFO, buttons=(btns), message_format=str1 ) - dia = gtk.Dialog( parent=self.main_window, flags=gtk.DIALOG_DESTROY_WITH_PARENT - , title="" ) # , buttons=btns - vbox = dia.vbox + # save in liststore + if status == 'ok': + iter = self.liststore.append( (dbms, name, comment, user, passwd, host, "", None, status, icon) ) + + # keep config save code in line with edited_cb()? call common routine? + + valid = True + # Validate new value (only for dbms so far, but dbms now not updateable so no validation at all!) + #if col == self.COL_DBMS: + # if new_text not in Configuration.DATABASE_TYPES: + # valid = False + + if valid: + self.config.add_db_parameters( db_server = dbms + , db_name = name + , db_desc = comment + , db_ip = host + , db_user = user + , db_pass = passwd ) + self.config.save() + self.changes = False + + + @staticmethod + def testDB(config, dbms, dbms_num, name, user, passwd, host): + status = "" + icon = None + err_msg = "" + + sql = SQL.Sql(db_server=dbms) + db = Database.Database(config, sql = sql, autoconnect = False) + # try to connect to db, set status and err_msg if it fails + try: + # is creating empty db for sqlite ... mod db.py further? + # add noDbTables flag to db.py? + log.debug(_("loaddbs: trying to connect to: %s/%s, %s, %s/%s") % (str(dbms_num),dbms,name,user,passwd)) + db.connect(backend=dbms_num, host=host, database=name, user=user, password=passwd, create=False) + if db.connected: + log.debug(_(" connected ok")) + status = 'ok' + icon = gtk.STOCK_APPLY + if db.wrongDbVersion: + status = 'old' + icon = gtk.STOCK_INFO + else: + log.debug(_(" not connected but no exception")) + except Exceptions.FpdbMySQLAccessDenied: + err_msg = _("MySQL Server reports: Access denied. Are your permissions set correctly?") + status = "failed" + icon = gtk.STOCK_CANCEL + except Exceptions.FpdbMySQLNoDatabase: + err_msg = _("MySQL client reports: 2002 or 2003 error. Unable to connect - Please check that the MySQL service has been started") + status = "failed" + icon = gtk.STOCK_CANCEL + except Exceptions.FpdbPostgresqlAccessDenied: + err_msg = _("Postgres Server reports: Access denied. Are your permissions set correctly?") + status = "failed" + except Exceptions.FpdbPostgresqlNoDatabase: + err_msg = _("Postgres client reports: Unable to connect - Please check that the Postgres service has been started") + status = "failed" + icon = gtk.STOCK_CANCEL + except: + # add more specific exceptions here if found (e.g. for sqlite?) + err = traceback.extract_tb(sys.exc_info()[2])[-1] + err_msg = err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1]) + status = "failed" + icon = gtk.STOCK_CANCEL + if err_msg: + log.info( _('db connection to ') + str(dbms_num)+','+host+','+name+','+user+','+passwd+' failed: ' + + err_msg ) + + return( status, err_msg, icon ) + + +class AddDB(gtk.Dialog): + + def __init__(self, config, parent): + log.debug("AddDB starting") + self.dbnames = { 'Sqlite' : Configuration.DATABASE_TYPE_SQLITE + , 'MySQL' : Configuration.DATABASE_TYPE_MYSQL + , 'PostgreSQL' : Configuration.DATABASE_TYPE_POSTGRESQL + } + self.config = config + # create dialog and add icon and label + super(AddDB,self).__init__( parent=parent + , flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT + , title="Add New Database" + , buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT + ,gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT) + ) # , buttons=btns + self.set_default_size(450, 280) + #self.connect('response', self.response_cb) + + t = gtk.Table(5, 3, True) + self.vbox.pack_start(t, expand=False, fill=False, padding=3) + + l = gtk.Label( _("DB Type") ) + l.set_alignment(1.0, 0.5) + t.attach(l, 0, 1, 0, 1, xpadding=3) + self.cb_dbms = gtk.combo_box_new_text() + for s in ('Sqlite',): # keys(self.dbnames): + self.cb_dbms.append_text(s) + self.cb_dbms.set_active(0) + t.attach(self.cb_dbms, 1, 3, 0, 1, xpadding=3) + self.cb_dbms.connect("changed", self.db_type_changed, None) + + l = gtk.Label( _("DB Name") ) + l.set_alignment(1.0, 0.5) + t.attach(l, 0, 1, 1, 2, xpadding=3) + self.e_db_name = gtk.Entry() + self.e_db_name.set_width_chars(15) + t.attach(self.e_db_name, 1, 3, 1, 2, xpadding=3) + self.e_db_name.connect("focus-out-event", self.db_name_changed, None) + + l = gtk.Label( _("DB Description") ) + l.set_alignment(1.0, 0.5) + t.attach(l, 0, 1, 2, 3, xpadding=3) + self.e_db_desc = gtk.Entry() + self.e_db_desc.set_width_chars(15) + t.attach(self.e_db_desc, 1, 3, 2, 3, xpadding=3) + + self.l_username = gtk.Label( _("Username") ) + self.l_username.set_alignment(1.0, 0.5) + t.attach(self.l_username, 0, 1, 3, 4, xpadding=3) + self.e_username = gtk.Entry() + self.e_username.set_width_chars(15) + t.attach(self.e_username, 1, 3, 3, 4, xpadding=3) + + self.l_password = gtk.Label( _("Password") ) + self.l_password.set_alignment(1.0, 0.5) + t.attach(self.l_password, 0, 1, 4, 5, xpadding=3) + self.e_password = gtk.Entry() + self.e_password.set_width_chars(15) + t.attach(self.e_password, 1, 3, 4, 5, xpadding=3) + + self.l_host = gtk.Label( _("Host Computer") ) + self.l_host.set_alignment(1.0, 0.5) + t.attach(self.l_host, 0, 1, 5, 6, xpadding=3) + self.e_host = gtk.Entry() + self.e_host.set_width_chars(15) + self.e_host.set_text("localhost") + t.attach(self.e_host, 1, 3, 5, 6, xpadding=3) + + parent.show_all() + self.show_all() + + # hide username/password fields as not used by sqlite + self.l_username.hide() + self.e_username.hide() + self.l_password.hide() + self.e_password.hide() + + def run(self): + response = super(AddDB,self).run() + log.debug("adddb.run: response is "+str(response)+" accept is "+str(int(gtk.RESPONSE_ACCEPT))) + + ok,retry = False,True + while response == gtk.RESPONSE_ACCEPT: + ok,retry = self.check_fields() + if retry: + response = super(AddDB,self).run() + else: + response = gtk.RESPONSE_REJECT + + (status, err_msg, icon, dbms, dbms_num + ,name, db_desc, user, passwd, host) = ("error", "error", None, None, None + ,None, None, None, None, None) + if ok: + log.debug("start creating new db") + # add a new db + master_password = None + dbms = self.dbnames[ self.cb_dbms.get_active_text() ] + dbms_num = self.config.get_backend(dbms) + name = self.e_db_name.get_text() + db_desc = self.e_db_desc.get_text() + user = self.e_username.get_text() + passwd = self.e_password.get_text() + host = self.e_host.get_text() - h = gtk.HBox(False, 2) - i = gtk.Image() - i.set_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DIALOG) - l = gtk.Label(str1) - h.pack_start(i, padding=5) - h.pack_start(l, padding=5) - vbox.pack_start(h) + # TODO: + # if self.cb_dbms.get_active_text() == 'Postgres': + # + + # create_db() in Database.py or here? ... TODO + + # test db after creating? + status, err_msg, icon = GuiDatabase.testDB(self.config, dbms, dbms_num, name, user, passwd, host) + log.debug('tested new db, result='+str((status,err_msg))) + if status == 'ok': + #dia = InfoBox( parent=self, str1=_('Database created') ) + str1 = _('Database created') + else: + #dia = InfoBox( parent=self, str1=_('Database creation failed') ) + str1 = _('Database creation failed') + #dia.add_msg("", True, True) + btns = (gtk.BUTTONS_OK) + dia = gtk.MessageDialog( parent=self, flags=gtk.DIALOG_DESTROY_WITH_PARENT + , type=gtk.MESSAGE_INFO, buttons=(btns), message_format=str1 ) + dia.run() + + return( (status, err_msg, icon, dbms, dbms_num, name, db_desc, user, passwd, host) ) + + def check_fields(self): + """check fields and return true/false according to whether user wants to try again + return False if fields are ok + """ + log.debug("check_fields: starting") + try_again = False + ok = True + + # checks for all db's + if self.e_db_name.get_text() == "": + msg = _("No Database Name given") + ok = False + elif self.e_db_desc.get_text() is None or self.e_db_desc.get_text() == "": + msg = _("No Database Description given") + ok = False + elif self.cb_dbms.get_active_text() != 'Sqlite' and self.e_username.get_text() == "": + msg = _("No Username given") + ok = False + elif self.cb_dbms.get_active_text() != 'Sqlite' and self.e_password.get_text() == "": + msg = _("No Password given") + ok = False + elif self.e_host.get_text() == "": + msg = _("No Host given") + ok = False + + if ok: + if self.cb_dbms.get_active_text() == 'Sqlite': + # checks for sqlite + pass + elif self.cb_dbms.get_active_text() == 'MySQL': + # checks for mysql + pass + elif self.cb_dbms.get_active_text() == 'Postgres': + # checks for postgres + pass + else: + msg = "Unknown Database Type selected" + ok = False + + if not ok: + log.debug("check_fields: open dialog") + dia = gtk.MessageDialog( parent=self + , flags=gtk.DIALOG_DESTROY_WITH_PARENT + , type=gtk.MESSAGE_ERROR + , message_format=msg + , buttons = gtk.BUTTONS_YES_NO + ) + #l = gtk.Label(msg) + #dia.vbox.add(l) + l = gtk.Label( _("Do you want to try again?") ) + dia.vbox.add(l) + dia.show_all() + ret = dia.run() + log.debug("check_fields: ret is "+str(ret)+" cancel is "+str(int(gtk.RESPONSE_CANCEL))) + if ret == gtk.RESPONSE_YES: + try_again = True + log.debug("check_fields: destroy dialog") + dia.hide() + dia.destroy() + + log.debug("check_fields: returning ok as "+str(ok)+", try_again as "+str(try_again)) + return(ok,try_again) + + def db_type_changed(self, widget, data): + if self.cb_dbms.get_active_text() == 'Sqlite': + self.l_username.hide() + self.e_username.hide() + self.e_username.set_text("") + self.l_password.hide() + self.e_password.hide() + self.e_password.set_text("") else: - # add extra label - vbox = dia.vbox - vbox.pack_start( gtk.Label(str1) ) - dia.show_all() + self.l_username.show() + self.e_username.show() + self.l_password.show() + self.e_password.show() + return(response) + + def db_name_changed(self, widget, event, data): + log.debug('db_name_changed: text='+widget.get_text()) + if not re.match('\....$', widget.get_text()): + widget.set_text(widget.get_text()+'.db3') + widget.show() + + #def response_cb(self, dialog, data): + # dialog.destroy() + # return(data) + + +class InfoBox(gtk.Dialog): + + def __init__(self, parent, str1): + # create dialog and add icon and label + btns = (gtk.BUTTONS_OK) + btns = None + # messagedialog puts text in inverse colors if no buttons are displayed?? + #dia = gtk.MessageDialog( parent=self.main_window, flags=gtk.DIALOG_DESTROY_WITH_PARENT + # , type=gtk.MESSAGE_INFO, buttons=(btns), message_format=str1 ) + # so just use Dialog instead + super(InfoBox,self).__init__( parent=parent + , flags=gtk.DIALOG_DESTROY_WITH_PARENT + , title="" ) # , buttons=btns + + h = gtk.HBox(False, 2) + i = gtk.Image() + i.set_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DIALOG) + l = gtk.Label(str1) + h.pack_start(i, padding=5) + h.pack_start(l, padding=5) + self.vbox.pack_start(h) + parent.show_all() + self.show_all() + + def add_msg(self, str1, run, destroy): + # add extra label + self.vbox.pack_start( gtk.Label(str1) ) + self.show_all() response = None - if run: response = dia.run() - if destroy: dia.destroy() - return (dia, response) + if run: response = self.run() + if destroy: self.destroy() + return (response) + + +class SideButton(gtk.Button): + """Create a button with the label below the icon""" + + # to change label on buttons: + # ( see http://faq.pygtk.org/index.py?req=show&file=faq09.005.htp ) + # gtk.stock_add([(gtk.STOCK_ADD, _("Add"), 0, 0, "")]) + + # alternatively: + # button = gtk.Button(stock=gtk.STOCK_CANCEL) + # button.show() + # alignment = button.get_children()[0] + # hbox = alignment.get_children()[0] + # image, label = hbox.get_children() + # label.set_text('Hide') + + def __init__(self, label=None, stock=None, use_underline=True): + gtk.stock_add([(stock, label, 0, 0, "")]) + + super(SideButton, self).__init__(label=label, stock=stock, use_underline=True) + alignment = self.get_children()[0] + hbox = alignment.get_children()[0] + image, label = hbox.get_children() + #label.set_text('Hide') + hbox.remove(image) + hbox.remove(label) + v = gtk.VBox(False, spacing=3) + v.pack_start(image, 3) + v.pack_start(label, 3) + alignment.remove(hbox) + alignment.add(v) + self.show_all() + if __name__=="__main__": diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index d9c7c07a..7ae5f044 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -62,6 +62,7 @@ class Hand(object): def __init__(self, config, sitename, gametype, handText, builtFrom = "HHC"): + #log.debug( _("Hand.init(): handText is ") + str(handText) ) self.config = config #log = Configuration.get_logger("logging.conf", "db", log_dir=self.config.dir_log) self.sitename = sitename @@ -314,7 +315,7 @@ If a player has None chips he won't be added.""" log.debug("markStreets:\n"+ str(self.streets)) else: tmp = self.handText[0:100] - log.error(_("markstreets didn't match - Assuming hand cancelled")) + log.error(_("markstreets didn't match - Assuming hand %s was cancelled") % self.handid) self.cancelled = True raise FpdbParseError(_("FpdbParseError: markStreets appeared to fail: First 100 chars: '%s'") % tmp) diff --git a/pyfpdb/OnGameToFpdb.py b/pyfpdb/OnGameToFpdb.py index 00ecf6ae..8ba8f9b4 100755 --- a/pyfpdb/OnGameToFpdb.py +++ b/pyfpdb/OnGameToFpdb.py @@ -19,6 +19,13 @@ ######################################################################## import sys +import exceptions + +import logging +# logging has been set up in fpdb.py or HUD_main.py, use their settings: +log = logging.getLogger("parser") + + import Configuration from HandHistoryConverter import * from decimal import Decimal @@ -65,7 +72,6 @@ class OnGame(HandHistoryConverter): # '5 Card Draw' : ('draw','fivedraw') } - #self.rexx.setGameInfoRegex('.*Blinds \$?(?P[.0-9]+)/\$?(?P[.0-9]+)') # Static regexes # ***** End of hand R5-75443872-57 ***** re_SplitHands = re.compile(u'\*\*\*\*\*\sEnd\sof\shand\s[-A-Z\d]+.*\n(?=\*)') @@ -73,6 +79,18 @@ class OnGame(HandHistoryConverter): # ***** History for hand R5-75443872-57 ***** # Start hand: Wed Aug 18 19:29:10 GMT+0100 2010 # Table: someplace [75443872] (LIMIT TEXAS_HOLDEM 0.50/1, Real money) +#***** History for hand R5-78042004-262 ***** +#Start hand: Fri Aug 27 21:40:46 GMT+0100 2010 +#Table: Bamako [78042004] (LIMIT TEXAS_HOLDEM $0.25/$0.50, Real money) +#User: sagi34 +#{ u'BB': None +#, u'DATETIME': u'Fri Aug 27 22:38:26 GMT+0100 2010\\n' +#, u'GAME': None +#, u'HID': u'R5-78042004-346' +#, u'TABLE': u'Bamako' +#, u'LIMIT': None +#, u'SB': None +#} re_HandInfo = re.compile(u""" \*\*\*\*\*\sHistory\sfor\shand\s(?P[-A-Z\d]+).* Start\shand:\s(?P.*) @@ -80,8 +98,8 @@ class OnGame(HandHistoryConverter): ( (?PNO_LIMIT|Limit|LIMIT|Pot\sLimit)\s (?PTEXAS_HOLDEM|RAZZ)\s - (?P[.0-9]+)/ - (?P[.0-9]+) + (%(LS)s)?(?P[.0-9]+)/ + (%(LS)s)?(?P[.0-9]+) )? """ % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE) @@ -102,7 +120,9 @@ class OnGame(HandHistoryConverter): # self.rexx.button_re = re.compile('#SUMMARY\nDealer: (?P.*)\n') #Seat 1: .Lucchess ($4.17 in chips) - re_PlayerInfo = re.compile(u'Seat (?P[0-9]+): (?P.*) \((?P[.0-9]+)\)') + #Seat 1: phantomaas ($27.11) + #Seat 5: mleo17 ($9.37) + re_PlayerInfo = re.compile(u'Seat (?P[0-9]+):\s(?P.*)\s\((%(LS)s)?(?P[.0-9]+)\)' % substitutions) def compilePlayerRegexs(self, hand): players = set([player[1] for player in hand.players]) @@ -117,23 +137,28 @@ class OnGame(HandHistoryConverter): #helander2222 posts blind ($0.25), lopllopl posts blind ($0.50). player_re = "(?P" + "|".join(map(re.escape, players)) + ")" subst = {'PLYR': player_re, 'CUR': self.sym[hand.gametype['currency']]} - self.re_PostSB = re.compile('(?P.*) posts small blind \(\$?(?P[.0-9]+)\)') - self.re_PostBB = re.compile('\), (?P.*) posts big blind \(\$?(?P[.0-9]+)\)') - self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante %(CUR)s(?P[.0-9]+)" % subst, re.MULTILINE) - self.re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for %(CUR)s(?P[.0-9]+)" % subst, re.MULTILINE) - self.re_PostBoth = re.compile('.*\n(?P.*): posts small \& big blinds \(\$? (?P[.0-9]+)\)') + self.re_PostSB = re.compile('(?P.*) posts small blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) + self.re_PostBB = re.compile('\), (?P.*) posts big blind \((%(CUR)s)?(?P[\.0-9]+)\)' % subst, re.MULTILINE) + self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante (%(CUR)s)?(?P[\.0-9]+)" % subst, re.MULTILINE) + self.re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for (%(CUR)s)?(?P[\.0-9]+)" % subst, re.MULTILINE) + self.re_PostBoth = re.compile('.*\n(?P.*): posts small \& big blinds \( (%(CUR)s)?(?P[\.0-9]+)\)' % subst) self.re_HeroCards = re.compile('Dealing\sto\s%(PLYR)s:\s\[(?P.*)\]' % subst) #lopllopl checks, Eurolll checks, .Lucchess checks. - self.re_Action = re.compile('(, )?(?P.*?)(?P bets| checks| raises| calls| folds)( (?P\d*\.?\d*))?( and is all-in)?') + #chumley. calls $0.25 + self.re_Action = re.compile('(, )?(?P.*?)(?P bets| checks| raises| calls| folds)( (%(CUR)s)?(?P[\d\.]+))?( and is all-in)?' % subst) #self.re_Board = re.compile(r"\[board cards (?P.+) \]") #Uchilka shows [ KC,JD ] self.re_ShowdownAction = re.compile('(?P.*) shows \[ (?P.+) \]') - # TODO: read SUMMARY correctly for collected pot stuff. - # Main pot: 6.75 won by player3 (6.45) - self.re_CollectPot = re.compile('Main pot: (?P\d*\.?\d*) won by %(PLYR)s' % subst) + #Main pot: $3.57 won by mleo17 ($3.40) + #Side pot 1: $3.26 won by maac_5 ($3.10) + #Main pot: $2.87 won by maac_5 ($1.37), sagi34 ($1.36) + self.re_Pot = re.compile('(Main|Side)\spot(\s\d+)?:\s.*won\sby\s(?P.*$)', re.MULTILINE) + self.re_CollectPot = re.compile('\s*(?P.*)\s\((%(CUR)s)?(?P[\.\d]+)\)' % subst) + #Seat 5: mleo17 ($3.40), net: +$2.57, [Jd, Qd] (TWO_PAIR QUEEN, JACK) + self.re_ShownCards = re.compile("^Seat (?P[0-9]+): (?P.*) \(.*\), net:.* \[(?P.*)\].*" % subst, re.MULTILINE) self.re_sitsOut = re.compile('(?P.*) sits out') def readSupportedGames(self): @@ -160,7 +185,13 @@ class OnGame(HandHistoryConverter): info['currency'] = 'USD' if 'LIMIT' in mg: - info['limitType'] = self.limits[mg['LIMIT']] + if mg['LIMIT'] in self.limits: + info['limitType'] = self.limits[mg['LIMIT']] + else: + tmp = handText[0:100] + log.error(_("determineGameType: limit not found in self.limits(%s). hand: '%s'") % (str(mg),tmp)) + log.error(_("determineGameType: Raising FpdbParseError")) + raise FpdbParseError(_("limit not found in self.limits(%s). hand: '%s'") % (str(mg),tmp)) if 'GAME' in mg: (info['base'], info['category']) = self.games[mg['GAME']] if 'SB' in mg: @@ -168,6 +199,7 @@ class OnGame(HandHistoryConverter): if 'BB' in mg: info['bb'] = mg['BB'] + #log.debug("determinegametype: returning "+str(info)) return info def readHandInfo(self, hand): @@ -177,7 +209,7 @@ class OnGame(HandHistoryConverter): if m: info.update(m.groupdict()) - log.debug("readHandInfo: %s" % info) + #log.debug("readHandInfo: %s" % info) for key in info: if key == 'DATETIME': #'Wed Aug 18 19:45:30 GMT+0100 2010 @@ -203,6 +235,7 @@ class OnGame(HandHistoryConverter): hand.mixed = None def readPlayerStacks(self, hand): + #log.debug("readplayerstacks: re is '%s'" % self.re_PlayerInfo) m = self.re_PlayerInfo.finditer(hand.handText) for a in m: hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH')) @@ -247,15 +280,13 @@ class OnGame(HandHistoryConverter): hand.setCommunityCards(street, m.group('CARDS').split(', ')) def readBlinds(self, hand): - log.debug( _("readBlinds starting") ) + #log.debug( _("readBlinds starting, hand=") + "\n["+hand.handText+"]" ) try: m = self.re_PostSB.search(hand.handText) - if m is None: - log.debug( _("re_postSB failed, hand=") + hand.handText ) hand.addBlind(m.group('PNAME'), 'small blind', m.group('SB')) - except: # no small blind - log.debug( _("readBlinds in noSB exception")+str(sys.exc_info()) ) - hand.addBlind(None, None, None) + except exceptions.AttributeError: # no small blind + log.debug( _("readBlinds in noSB exception - no SB created")+str(sys.exc_info()) ) + #hand.addBlind(None, None, None) for a in self.re_PostBB.finditer(hand.handText): hand.addBlind(a.group('PNAME'), 'big blind', a.group('BB')) for a in self.re_PostBoth.finditer(hand.handText): @@ -289,7 +320,7 @@ class OnGame(HandHistoryConverter): m = self.re_Action.finditer(hand.streets[street]) for action in m: acts = action.groupdict() - #print "DEBUG: acts: %s" %acts + #log.debug("readaction: acts: %s" %acts) if action.group('ATYPE') == ' raises': hand.addRaiseBy( street, action.group('PNAME'), action.group('BET') ) elif action.group('ATYPE') == ' calls': @@ -314,18 +345,22 @@ class OnGame(HandHistoryConverter): hand.addShownCards(cards, shows.group('PNAME')) def readCollectPot(self,hand): - for m in self.re_CollectPot.finditer(hand.handText): - hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) + for m in self.re_Pot.finditer(hand.handText): + for splitpot in m.group('POT').split(','): + for m in self.re_CollectPot.finditer(splitpot): + hand.addCollectPot(player=m.group('PNAME'),pot=m.group('POT')) def readShownCards(self,hand): - return - #for m in self.rexx.collect_pot_re.finditer(hand.string): - #if m.group('CARDS') is not None: - #cards = m.group('CARDS') - #cards = set(cards.split(',')) - #hand.addShownCards(cards=None, player=m.group('PNAME'), holeandboard=cards) + for m in self.re_ShownCards.finditer(hand.handText): + cards = m.group('CARDS') + cards = cards.split(', ') # needs to be a list, not a set--stud needs the order + + (shown, mucked) = (False, False) + if m.group('CARDS') is not None: + shown = True + hand.addShownCards(cards=cards, player=m.group('PNAME'), shown=shown, mucked=mucked) + - if __name__ == "__main__":