From 68835dd9b809cdd99165c511d4b750f6ebdc7287 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Mon, 7 Dec 2009 22:55:12 +0000 Subject: [PATCH 1/3] tidy up log viewer some more --- pyfpdb/GuiLogView.py | 25 ++++++++++++++++---- pyfpdb/fpdb.py | 55 ++++++++++++++++++++++++-------------------- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/pyfpdb/GuiLogView.py b/pyfpdb/GuiLogView.py index 3b7b8aa6..8f4cd834 100755 --- a/pyfpdb/GuiLogView.py +++ b/pyfpdb/GuiLogView.py @@ -18,6 +18,7 @@ import mmap +import threading import pygtk pygtk.require('2.0') @@ -33,14 +34,20 @@ MAX_LINES = 100000 class GuiLogView: - def __init__(self, config, mainwin, vbox): + def __init__(self, config, mainwin): self.config = config self.main_window = mainwin - self.vbox = vbox + + self.dia = gtk.Dialog(title="Log Messages" + ,parent=None + ,flags=gtk.DIALOG_DESTROY_WITH_PARENT + ,buttons=(gtk.STOCK_CLOSE,gtk.RESPONSE_OK)) + self.dia.set_modal(False) + + self.vbox = self.dia.vbox gtk.Widget.set_size_request(self.vbox, 700, 400); self.liststore = gtk.ListStore(str, str, str, str, gobject.TYPE_BOOLEAN) # date, module, level, text - # this is how to add a filter: # # # Creation of the filter, from the model @@ -49,7 +56,6 @@ class GuiLogView: # # # The TreeView gets the filter as model # self.listview = gtk.TreeView(filter) - self.listview = gtk.TreeView(model=self.liststore) self.listview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_NONE) self.listcols = [] @@ -67,6 +73,7 @@ class GuiLogView: self.listview.show() scrolledwindow.show() self.vbox.show() + self.dia.set_focus(self.listview) col = self.addColumn("Date/Time", 0) col = self.addColumn("Module", 1) @@ -75,6 +82,16 @@ class GuiLogView: self.loadLog() self.vbox.show_all() + self.dia.show() + + self.dia.connect('response', self.dialog_response_cb) + + def dialog_response_cb(self, dialog, response_id): + # this is called whether close button is pressed or window is closed + dialog.destroy() + + def get_dialog(self): + return self.dia def addColumn(self, title, n): col = gtk.TreeViewColumn(title) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 79567578..2266a981 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -101,12 +101,12 @@ class fpdb: def add_tab(self, new_page, new_tab_name): """adds a tab, namely creates the button and displays it and appends all the relevant arrays""" - for name in self.nb_tabs: #todo: check this is valid + for name in self.nb_tab_names: #todo: check this is valid if name == new_tab_name: return # if tab already exists, just go to it used_before = False - for i, name in enumerate(self.tab_names): #todo: check this is valid + for i, name in enumerate(self.tab_names): if name == new_tab_name: used_before = True event_box = self.tabs[i] @@ -122,13 +122,13 @@ class fpdb: #self.nb.append_page(new_page, gtk.Label(new_tab_name)) self.nb.append_page(page, event_box) - self.nb_tabs.append(new_tab_name) + self.nb_tab_names.append(new_tab_name) page.show() def display_tab(self, new_tab_name): """displays the indicated tab""" tab_no = -1 - for i, name in enumerate(self.nb_tabs): + for i, name in enumerate(self.nb_tab_names): if new_tab_name == name: tab_no = i break @@ -179,13 +179,13 @@ class fpdb: (nb, text) = data page = -1 #print "\n remove_tab: start", text - for i, tab in enumerate(self.nb_tabs): + for i, tab in enumerate(self.nb_tab_names): if text == tab: page = i #print " page =", page if page >= 0 and page < self.nb.get_n_pages(): #print " removing page", page - del self.nb_tabs[page] + del self.nb_tab_names[page] nb.remove_page(page) # Need to refresh the widget -- # This forces the widget to redraw itself. @@ -210,6 +210,9 @@ class fpdb: dia.set_program_name("FPDB") dia.run() dia.destroy() + log.debug("Threads: ") + for t in self.threads: + log.debug("........." + str(t.__class__)) def dia_preferences(self, widget, data=None): dia = gtk.Dialog("Preferences", @@ -418,22 +421,23 @@ class fpdb: self.release_global_lock() def dia_logs(self, widget, data=None): - lock_set = False - if self.obtain_global_lock(): - lock_set = True + """opens the log viewer window""" - dia = gtk.Dialog(title="Log Messages" - ,parent=None - ,flags=0 - ,buttons=(gtk.STOCK_CLOSE,gtk.RESPONSE_OK)) - logviewer = GuiLogView.GuiLogView(self.config, self.window, dia.vbox) - response = dia.run() - if response == gtk.RESPONSE_ACCEPT: - pass - dia.destroy() + #lock_set = False + #if self.obtain_global_lock(): + # lock_set = True - if lock_set: - self.release_global_lock() + for i, t in enumerate(self.threads): + if str(t.__class__) == 'GuiLogView.GuiLogView': + # show existing log window + t.get_dialog().present() + return + + new_thread = GuiLogView.GuiLogView(self.config, self.window) + self.threads.append(new_thread) + + #if lock_set: + # self.release_global_lock() def addLogText(self, text): end_iter = self.logbuffer.get_end_iter() @@ -758,7 +762,6 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.add_and_display_tab(gv_tab, "Graphs") def __init__(self): - self.threads = [] # no more than 1 process can this lock at a time: self.lock = interlocks.InterProcessLock(name="fpdb_global_lock") self.db = None @@ -782,14 +785,16 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") menubar.show() #done menubar + self.threads = [] # objects used by tabs - no need for threads, gtk handles it + self.nb = gtk.Notebook() self.nb.set_show_tabs(True) self.nb.show() self.main_vbox.pack_start(self.nb, True, True, 0) - self.pages=[] - self.tabs=[] - self.tab_names=[] - self.nb_tabs=[] + self.tabs=[] # the event_boxes forming the actual tabs + self.tab_names=[] # names of tabs used since program started, not removed if tab is closed + self.pages=[] # the contents of the page, not removed if tab is closed + self.nb_tab_names=[] # list of tab names currently displayed in notebook self.tab_main_help(None, None) From bbaecc1697fee02b3d937b07e9228ebb97599775 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Tue, 8 Dec 2009 22:17:55 +0000 Subject: [PATCH 2/3] add package version info to About dialog --- pyfpdb/fpdb.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index d40f65bd..8b21a8c7 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -73,6 +73,7 @@ try: import pygtk pygtk.require('2.0') import gtk + import pango except: print "Unable to load PYGTK modules required for GUI. Please install PyCairo, PyGObject, and PyGTK from www.pygtk.org." raw_input("Press ENTER to continue.") @@ -80,6 +81,24 @@ except: import interlocks +# these imports not required in this module, imported here to report version in About dialog +try: + import matplotlib + matplotlib_version = matplotlib.__version__ +except: + matplotlib_version = 'not found' +try: + import numpy + numpy_version = numpy.__version__ +except: + numpy_version = 'not found' +try: + import sqlite3 + sqlite3_version = sqlite3.version + sqlite_version = sqlite3.sqlite_version +except: + sqlite3_version = 'not found' + sqlite_version = 'not found' import GuiPrefs import GuiLogView @@ -219,6 +238,34 @@ class fpdb: dia.set_website("http://fpdb.sourceforge.net/") dia.set_authors("Steffen, Eratosthenes, s0rrow, EricBlade, _mt, sqlcoder, Bostik, and others") dia.set_program_name("Free Poker Database (FPDB)") + + db_version = "" + #if self.db is not None: + # db_version = self.db.get_version() + nums = [ ('Operating System', os.name) + , ('Python', sys.version[0:3]) + , ('GTK+', '.'.join([str(x) for x in gtk.gtk_version])) + , ('PyGTK', '.'.join([str(x) for x in gtk.pygtk_version])) + , ('matplotlib', matplotlib_version) + , ('numpy', numpy_version) + , ('sqlite3', sqlite3_version) + , ('sqlite', sqlite_version) + , ('database', self.settings['db-server'] + db_version) + ] + versions = gtk.TextBuffer() + w = 20 # width used for module names and version numbers + versions.set_text( '\n'.join( [x[0].rjust(w)+' '+ x[1].ljust(w) for x in nums] ) ) + view = gtk.TextView(versions) + view.set_editable(False) + view.set_justification(gtk.JUSTIFY_CENTER) + view.modify_font(pango.FontDescription('monospace 10')) + view.show() + dia.vbox.pack_end(view, True, True, 2) + l = gtk.Label('Version Information:') + l.set_alignment(0.5, 0.5) + l.show() + dia.vbox.pack_end(l, True, True, 2) + dia.run() dia.destroy() log.debug("Threads: ") @@ -636,9 +683,11 @@ class fpdb: 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() 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 From fc95de82f475ae1fb8818107d94d92bf064cdfe0 Mon Sep 17 00:00:00 2001 From: sqlcoder Date: Wed, 9 Dec 2009 21:58:56 +0000 Subject: [PATCH 3/3] logviewer: avoid memory-mapped files, make it work when opened a second time --- pyfpdb/GuiLogView.py | 57 +++++++++++++++++++------------------------- pyfpdb/fpdb.py | 35 +++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 37 deletions(-) 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/fpdb.py b/pyfpdb/fpdb.py index 8b21a8c7..b7013a92 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?) @@ -485,14 +486,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 +511,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) @@ -846,6 +870,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)