diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 36fdd2d2..ad376453 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -187,6 +187,7 @@ class Database: def __init__(self, c, sql = None): log.info("Creating Database instance, sql = %s" % sql) self.config = c + self.__connected = False self.fdb = fpdb_db.fpdb_db() # sets self.fdb.db self.fdb.cursor and self.fdb.sql self.do_connect(c) @@ -237,7 +238,12 @@ class Database: self.hud_style = style def do_connect(self, c): - self.fdb.do_connect(c) + try: + self.fdb.do_connect(c) + except: + # error during connect + self.__connected = False + raise self.connection = self.fdb.db self.wrongDbVersion = self.fdb.wrongDbVersion @@ -247,6 +253,7 @@ class Database: self.db_server = db_params['db-server'] self.database = db_params['db-databaseName'] self.host = db_params['db-host'] + self.__connected = True def commit(self): self.fdb.db.commit() @@ -254,6 +261,9 @@ class Database: def rollback(self): self.fdb.db.rollback() + def connected(self): + return self.__connected + def get_cursor(self): return self.connection.cursor() diff --git a/pyfpdb/Exceptions.py b/pyfpdb/Exceptions.py index 5b4f4391..fd7c20e3 100644 --- a/pyfpdb/Exceptions.py +++ b/pyfpdb/Exceptions.py @@ -34,5 +34,19 @@ class FpdbMySQLNoDatabase(FpdbDatabaseError): def __str__(self): return repr(self.value +" " + self.errmsg) +class FpdbPostgresqlAccessDenied(FpdbDatabaseError): + def __init__(self, value='', errmsg=''): + self.value = value + self.errmsg = errmsg + def __str__(self): + return repr(self.value +" " + self.errmsg) + +class FpdbPostgresqlNoDatabase(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/GuiLogView.py b/pyfpdb/GuiLogView.py index 8f4cd834..29385fa0 100755 --- a/pyfpdb/GuiLogView.py +++ b/pyfpdb/GuiLogView.py @@ -17,8 +17,8 @@ #agpl-3.0.txt in the docs folder of the package. -import mmap -import threading +import os +import Queue import pygtk pygtk.require('2.0') @@ -30,13 +30,16 @@ import Configuration log = Configuration.get_logger("logging.conf", "logview") -MAX_LINES = 100000 +MAX_LINES = 100000 # max lines to display in window +EST_CHARS_PER_LINE = 150 # used to guesstimate number of lines in log file +logfile = 'logging.out' # name of logfile class GuiLogView: - def __init__(self, config, mainwin): + def __init__(self, config, mainwin, closeq): self.config = config self.main_window = mainwin + self.closeq = closeq self.dia = gtk.Dialog(title="Log Messages" ,parent=None @@ -88,6 +91,7 @@ class GuiLogView: def dialog_response_cb(self, dialog, response_id): # this is called whether close button is pressed or window is closed + self.closeq.put(self.__class__) dialog.destroy() def get_dialog(self): @@ -109,39 +113,28 @@ class GuiLogView: def loadLog(self): - #self.configStore = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING) - #self.configView = gtk.TreeView(self.configStore) - #self.configView.set_enable_tree_lines(True) self.liststore.clear() self.listcols = [] - # count number of lines in file - f = open('logging.out', "r+") - buf = mmap.mmap(f.fileno(), 0) - readline = buf.readline - lines = 0 - while readline(): - lines += 1 - f.close() + # guesstimate number of lines in file + if os.path.exists(logfile): + stat_info = os.stat(logfile) + lines = stat_info.st_size / EST_CHARS_PER_LINE + print "logview: size =", stat_info.st_size, "lines =", lines - startline = 0 - if lines > MAX_LINES: - # only display from startline if log file is large - startline = lines - MAX_LINES + # set startline to line number to start display from + startline = 0 + if lines > MAX_LINES: + # only display from startline if log file is large + startline = lines - MAX_LINES - f = open('logging.out', "r+") - buf = mmap.mmap(f.fileno(), 0) - readline = buf.readline - l = 0 - line = readline() - while line: - # eg line: - # 2009-12-02 15:23:21,716 - config DEBUG config logger initialised - l = l + 1 - if l > startline and len(line) > 49: - iter = self.liststore.append( (line[0:23], line[26:32], line[39:46], line[48:].strip(), True) ) - line = readline() - f.close() + l = 0 + for line in open(logfile): + # eg line: + # 2009-12-02 15:23:21,716 - config DEBUG config logger initialised + l = l + 1 + if l > startline and len(line) > 49: + iter = self.liststore.append( (line[0:23], line[26:32], line[39:46], line[48:].strip(), True) ) def sortCols(self, col, n): try: diff --git a/pyfpdb/GuiPrefs.py b/pyfpdb/GuiPrefs.py index ada9cb51..b85b3f6a 100755 --- a/pyfpdb/GuiPrefs.py +++ b/pyfpdb/GuiPrefs.py @@ -91,10 +91,15 @@ class GuiPrefs: #iter = self.configStore.append( parent, [node.nodeValue, None] ) iter = None if node.nodeType != node.TEXT_NODE and node.nodeType != node.COMMENT_NODE: + name = "" iter = self.configStore.append( parent, [node, setting, value] ) if node.hasAttributes(): for i in xrange(node.attributes.length): self.configStore.append( iter, [node, node.attributes.item(i).localName, node.attributes.item(i).value] ) + if node.attributes.item(i).localName in ('site_name', 'game_name', 'stat_name', 'name', 'db_server', 'site'): + name = " " + node.attributes.item(i).value + if name != "": + self.configStore.set_value(iter, 1, setting+name) if node.hasChildNodes(): for elem in node.childNodes: self.addTreeRows(iter, elem) @@ -156,7 +161,7 @@ if __name__=="__main__": gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) - dia.set_default_size(500, 500) + dia.set_default_size(700, 500) prefs = GuiPrefs(config, win, dia.vbox) response = dia.run() if response == gtk.RESPONSE_ACCEPT: diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 8b21a8c7..e93c9c03 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -18,6 +18,7 @@ import os import sys import re +import Queue # if path is set to use an old version of python look for a new one: # (does this work in linux?) @@ -278,12 +279,20 @@ class fpdb: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) - dia.set_default_size(500, 500) + dia.set_default_size(700, 500) + prefs = GuiPrefs.GuiPrefs(self.config, self.window, dia.vbox) response = dia.run() if response == gtk.RESPONSE_ACCEPT: # save updated config self.config.save() + if len(self.nb_tab_names) == 1: + # only main tab open, reload profile + self.load_profile() + else: + self.warning_box("Updated preferences have not been loaded because " + + "windows are open. Re-start fpdb to load them.") + dia.destroy() def dia_create_del_database(self, widget, data=None): @@ -485,14 +494,22 @@ class fpdb: #if self.obtain_global_lock(): # lock_set = True + # remove members from self.threads if close messages received + self.process_close_messages() + + viewer = None for i, t in enumerate(self.threads): if str(t.__class__) == 'GuiLogView.GuiLogView': - # show existing log window - t.get_dialog().present() - return + viewer = t + break - new_thread = GuiLogView.GuiLogView(self.config, self.window) - self.threads.append(new_thread) + if viewer is None: + #print "creating new log viewer" + new_thread = GuiLogView.GuiLogView(self.config, self.window, self.closeq) + self.threads.append(new_thread) + else: + #print "showing existing log viewer" + viewer.get_dialog().present() #if lock_set: # self.release_global_lock() @@ -502,6 +519,21 @@ class fpdb: self.logbuffer.insert(end_iter, text) self.logview.scroll_to_mark(self.logbuffer.get_insert(), 0) + + def process_close_messages(self): + # check for close messages + try: + while True: + name = self.closeq.get(False) + for i, t in enumerate(self.threads): + if str(t.__class__) == str(name): + # thread has ended so remove from list: + del self.threads[i] + break + except Queue.Empty: + # no close messages on queue, do nothing + pass + def __calendar_dialog(self, widget, entry): self.dia_confirm.set_modal(False) d = gtk.Window(gtk.WINDOW_TOPLEVEL) @@ -622,7 +654,7 @@ class fpdb: ('LoadProf', None, '_Load Profile (broken)', 'L', 'Load your profile', self.dia_load_profile), ('EditProf', None, '_Edit Profile (todo)', 'E', 'Edit your profile', self.dia_edit_profile), ('SaveProf', None, '_Save Profile (todo)', 'S', 'Save your profile', self.dia_save_profile), - ('Preferences', None, '_Preferences', None, 'Edit your preferences', self.dia_preferences), + ('Preferences', None, 'Pre_ferences', 'F', 'Edit your preferences', self.dia_preferences), ('import', None, '_Import'), ('sethharchive', None, '_Set HandHistory Archive Directory', None, 'Set HandHistory Archive Directory', self.select_hhArchiveBase), ('bulkimp', None, '_Bulk Import', 'B', 'Bulk Import', self.tab_bulk_import), @@ -676,21 +708,26 @@ class fpdb: self.settings.update(self.config.get_import_parameters()) self.settings.update(self.config.get_default_paths()) - if self.db is not None and self.db.fdb is not None: + if self.db is not None and self.db.connected: self.db.disconnect() self.sql = SQL.Sql(db_server = self.settings['db-server']) + err_msg = None try: self.db = Database.Database(self.config, sql = self.sql) except Exceptions.FpdbMySQLAccessDenied: - #self.db = None - self.warning_box("MySQL Server reports: Access denied. Are your permissions set correctly?") - exit() + err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?" except Exceptions.FpdbMySQLNoDatabase: - #self.db = None - msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - Please check that the MySQL service has been started" - self.warning_box(msg) - exit + err_msg = "MySQL client reports: 2002 or 2003 error. Unable to connect - " \ + + "Please check that the MySQL service has been started" + except Exceptions.FpdbPostgresqlAccessDenied: + err_msg = "Postgres Server reports: Access denied. Are your permissions set correctly?" + except Exceptions.FpdbPostgresqlNoDatabase: + err_msg = "Postgres client reports: Unable to connect - " \ + + "Please check that the Postgres service has been started" + if err_msg is not None: + self.db = None + self.warning_box(err_msg) # except FpdbMySQLFailedError: # self.warning_box("Unable to connect to MySQL! Is the MySQL server running?!", "FPDB ERROR") @@ -708,7 +745,7 @@ class fpdb: # 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: + if self.db is not None and self.db.wrongDbVersion: diaDbVersionWarning = gtk.Dialog(title="Strong Warning - Invalid database version", parent=None, flags=0, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK)) label = gtk.Label("An invalid DB version or missing tables have been detected.") @@ -727,14 +764,15 @@ class fpdb: diaDbVersionWarning.destroy() if self.status_bar is None: - self.status_bar = gtk.Label("Status: Connected to %s database named %s on host %s"%(self.db.get_backend_name(),self.db.database, self.db.host)) + self.status_bar = gtk.Label("") self.main_vbox.pack_end(self.status_bar, False, True, 0) self.status_bar.show() - else: - self.status_bar.set_text("Status: Connected to %s database named %s on host %s" % (self.db.get_backend_name(),self.db.database, self.db.host)) - # Database connected to successfully, load queries to pass on to other classes - self.db.rollback() + if self.db is not None and self.db.connected: + self.status_bar.set_text("Status: Connected to %s database named %s on host %s" + % (self.db.get_backend_name(),self.db.database, self.db.host)) + # rollback to make sure any locks are cleared: + self.db.rollback() self.validate_config() @@ -755,7 +793,8 @@ class fpdb: # TODO: can we get some / all of the stuff done in this function to execute on any kind of abort? print "Quitting normally" # TODO: check if current settings differ from profile, if so offer to save or abort - self.db.disconnect() + if self.db is not None and self.db.connected: + self.db.disconnect() self.statusIcon.set_visible(False) gtk.main_quit() @@ -846,6 +885,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") #done menubar self.threads = [] # objects used by tabs - no need for threads, gtk handles it + self.closeq = Queue.Queue(20) # used to signal ending of a thread (only logviewer for now) self.nb = gtk.Notebook() self.nb.set_show_tabs(True) diff --git a/pyfpdb/fpdb_db.py b/pyfpdb/fpdb_db.py index 405a142c..baabb4bf 100644 --- a/pyfpdb/fpdb_db.py +++ b/pyfpdb/fpdb_db.py @@ -129,18 +129,22 @@ class fpdb_db: self.db = psycopg2.connect(database = database) connected = True except: + # direct connection failed so try user/pass/... version pass - #msg = "PostgreSQL direct connection to database (%s) failed, trying with user ..." % (database,) - #print msg - #raise FpdbError(msg) if not connected: try: self.db = psycopg2.connect(host = host, 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) + except Exception, ex: + if 'Connection refused' in ex.args[0]: + # meaning eg. db not running + raise FpdbPostgresqlNoDatabase(errmsg = ex.args[0]) + elif 'password authentication' in ex.args[0]: + raise FpdbPostgresqlAccessDenied(errmsg = ex.args[0]) + else: + msg = ex.args[0] print msg raise FpdbError(msg) elif backend == fpdb_db.SQLITE: @@ -167,6 +171,7 @@ class fpdb_db: logging.warning("Some database functions will not work without NumPy support") else: raise FpdbError("unrecognised database backend:"+backend) + self.cursor = self.db.cursor() # Set up query dictionary as early in the connection process as we can. self.sql = FpdbSQLQueries.FpdbSQLQueries(self.get_backend_name()) diff --git a/pyfpdb/fpdb_simple.py b/pyfpdb/fpdb_simple.py index aa9421f6..1c22f6e6 100644 --- a/pyfpdb/fpdb_simple.py +++ b/pyfpdb/fpdb_simple.py @@ -1247,13 +1247,13 @@ sure to also change the following storage method and table_viewer.prepare_data i if not isAllIn: isAllIn = any(i for i in allIns[1][player]) - elif len(action_types[2][player]) > 0: + if isAllIn or len(action_types[2][player]) > 0: if all(actiontype != "fold" for actiontype in action_types[1][player]): myStreet2Seen = True if not isAllIn: isAllAin = any(i for i in allIns[2][player]) - elif len(action_types[3][player]) > 0: + if isAllIn or len(action_types[3][player]) > 0: if all(actiontype != "fold" for actiontype in action_types[2][player]): myStreet3Seen = True @@ -1264,7 +1264,7 @@ sure to also change the following storage method and table_viewer.prepare_data i #print "in else" if not isAllIn: isAllIn = any(i for i in allIns[3][player]) - elif len(action_types[4][player]) > 0: + if isAllIn or len(action_types[4][player]) > 0: #print "in if" myStreet4Seen = True