diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index cd6ad298..36fdd2d2 100755 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -58,7 +58,8 @@ class Database: PGSQL = 3 SQLITE = 4 - hero_hudstart_def = '1999-12-31' # default for length of Hero's stats in HUD + hero_hudstart_def = '1999-12-31' # default for length of Hero's stats in HUD + villain_hudstart_def = '1999-12-31' # default for length of Villain's stats in HUD # Data Structures for index and foreign key creation # drop_code is an int with possible values: 0 - don't drop for bulk import @@ -473,8 +474,9 @@ class Database: else: h_seats_min, h_seats_max = 0, 10 print "bad h_seats_style value:", h_seats_style - print "opp seats style", seats_style, "hero seats style", h_seats_style - print "opp seats:", seats_min, seats_max, " hero seats:", h_seats_min, h_seats_max + log.info("opp seats style %s %d %d hero seats style %s %d %d" + % (seats_style, seats_min, seats_max + ,h_seats_style, h_seats_min, h_seats_max) ) if hud_style == 'S' or h_hud_style == 'S': self.get_stats_from_hand_session(hand, stat_dict, hero_id @@ -1324,7 +1326,7 @@ class Database: self.dropAllForeignKeys() self.createAllForeignKeys() - def rebuild_hudcache(self, start=None): + def rebuild_hudcache(self, h_start=None, v_start=None): """clears hudcache and rebuilds from the individual handsplayers records""" try: @@ -1344,13 +1346,17 @@ class Database: if p_id: self.hero_ids[site_id] = int(p_id) - if start is None: - start = self.hero_hudstart_def + if h_start is None: + h_start = self.hero_hudstart_def + if v_start is None: + v_start = self.villain_hudstart_def if self.hero_ids == {}: where = "" else: - where = "where hp.playerId not in " + str(tuple(self.hero_ids.values())) \ - + " or h.handStart > '" + start + "'" + where = "where ( hp.playerId not in " + str(tuple(self.hero_ids.values())) \ + + " and h.handStart > '" + v_start + "')" \ + + " or ( hp.playerId in " + str(tuple(self.hero_ids.values())) \ + + " and h.handStart > '" + h_start + "')" rebuild_sql = self.sql.query['rebuildHudCache'].replace('', where) self.get_cursor().execute(self.sql.query['clearHudCache']) diff --git a/pyfpdb/GuiGraphViewer.py b/pyfpdb/GuiGraphViewer.py index 76c80f2e..9daa940f 100644 --- a/pyfpdb/GuiGraphViewer.py +++ b/pyfpdb/GuiGraphViewer.py @@ -196,7 +196,10 @@ class GuiGraphViewer (threading.Thread): self.ax.plot(green, color='green', label='Hands: %d\nProfit: $%.2f' %(len(green), green[-1])) self.ax.plot(blue, color='blue', label='Showdown: $%.2f' %(blue[-1])) self.ax.plot(red, color='red', label='Non-showdown: $%.2f' %(red[-1])) - self.ax.legend(loc='best', fancybox=True, shadow=True, prop=FontProperties(size='smaller')) + if sys.version[0:3] == '2.5': + self.ax.legend(loc='best', shadow=True, prop=FontProperties(size='smaller')) + else: + self.ax.legend(loc='best', fancybox=True, shadow=True, prop=FontProperties(size='smaller')) self.graphBox.add(self.canvas) diff --git a/pyfpdb/GuiLogView.py b/pyfpdb/GuiLogView.py new file mode 100755 index 00000000..3b7b8aa6 --- /dev/null +++ b/pyfpdb/GuiLogView.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +#Copyright 2008 Carl Gherardi +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU Affero General Public License as published by +#the Free Software Foundation, version 3 of the License. +# +#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 Affero General Public License +#along with this program. If not, see . +#In the "official" distribution you can find the license in +#agpl-3.0.txt in the docs folder of the package. + + +import mmap + +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import pango + +import Configuration + +log = Configuration.get_logger("logging.conf", "logview") + +MAX_LINES = 100000 + +class GuiLogView: + + def __init__(self, config, mainwin, vbox): + self.config = config + self.main_window = mainwin + self.vbox = 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 + # filter = self.liststore.filter_new() + # filter.set_visible_column(1) + # + # # 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 = [] + + scrolledwindow = gtk.ScrolledWindow() + scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledwindow.add(self.listview) + self.vbox.pack_start(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() + + self.listview.show() + scrolledwindow.show() + self.vbox.show() + + col = self.addColumn("Date/Time", 0) + col = self.addColumn("Module", 1) + col = self.addColumn("Level", 2) + col = self.addColumn("Text", 3) + + self.loadLog() + self.vbox.show_all() + + def addColumn(self, title, n): + col = gtk.TreeViewColumn(title) + self.listview.append_column(col) + cRender = gtk.CellRendererText() + cRender.set_property("wrap-mode", pango.WRAP_WORD_CHAR) + col.pack_start(cRender, True) + col.add_attribute(cRender, 'text', n) + col.set_max_width(1000) + col.set_spacing(0) # no effect + self.listcols.append(col) + col.set_clickable(True) + col.connect("clicked", self.sortCols, n) + return(col) + + 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() + + 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() + + def sortCols(self, col, n): + try: + if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING: + col.set_sort_order(gtk.SORT_DESCENDING) + else: + col.set_sort_order(gtk.SORT_ASCENDING) + self.liststore.set_sort_column_id(n, col.get_sort_order()) + #self.liststore.set_sort_func(n, self.sortnums, (n,grid)) + for i in xrange(len(self.listcols)): + self.listcols[i].set_sort_indicator(False) + self.listcols[n].set_sort_indicator(True) + # use this listcols[col].set_sort_indicator(True) + # to turn indicator off for other cols + except: + err = traceback.extract_tb(sys.exc_info()[2]) + print "***sortCols error: " + str(sys.exc_info()[1]) + print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] ) + + def refresh(self, widget, data): + self.loadLog() + + + +if __name__=="__main__": + + config = Configuration.Config() + + win = gtk.Window(gtk.WINDOW_TOPLEVEL) + win.set_title("Test Log Viewer") + win.set_border_width(1) + win.set_default_size(600, 500) + win.set_resizable(True) + + dia = gtk.Dialog("Log Viewer", + win, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) + dia.set_default_size(500, 500) + log = GuiLogView(config, win, dia.vbox) + response = dia.run() + if response == gtk.RESPONSE_ACCEPT: + pass + dia.destroy() + + + + diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 7e2d5fa6..c3599a3b 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -63,6 +63,9 @@ elif os.name == 'nt': import Hud +log = Configuration.get_logger("logging.conf") + + class HUD_main(object): """A main() object to own both the read_stdin thread and the gui.""" # This class mainly provides state for controlling the multiple HUDs. @@ -192,6 +195,8 @@ class HUD_main(object): while 1: # wait for a new hand number on stdin new_hand_id = sys.stdin.readline() + t0 = time.time() + t1 = t2 = t3 = t4 = t5 = t6 = t0 new_hand_id = string.rstrip(new_hand_id) if new_hand_id == "": # blank line means quit self.destroy() @@ -206,6 +211,7 @@ class HUD_main(object): print "db error: skipping %s" % new_hand_id sys.stderr.write("Database error: could not find hand %s.\n" % new_hand_id) continue + t1 = time.time() if type == "tour": # hand is from a tournament temp_key = tour_number @@ -215,6 +221,12 @@ 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 + 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']) + t2 = time.time() + 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], num_seats) + t3 = time.time() try: 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']) @@ -238,8 +250,10 @@ class HUD_main(object): else: # get stats using default params--also get cards self.db_connection.init_hud_stat_vars( self.hud_params['hud_days'], self.hud_params['h_hud_days'] ) + t4 = time.time() stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_params ,self.hero_ids[site_id], num_seats) + t5 = time.time() cards = self.db_connection.get_cards(new_hand_id) comm_cards = self.db_connection.get_common_cards(new_hand_id) if comm_cards != {}: # stud! @@ -263,6 +277,9 @@ class HUD_main(object): else: sys.stderr.write('Table "%s" no longer exists\n' % table_name) + t6 = time.time() + log.info("HUD_main.read_stdin: hand read in %4.3f seconds (%4.3f,%4.3f,%4.3f,%4.3f,%4.3f,%4.3f)" + % (t6-t0,t1-t0,t2-t0,t3-t0,t4-t0,t5-t0,t6-t0)) self.db_connection.connection.rollback() if __name__== "__main__": diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py index eb9296ef..5a0d1965 100644 --- a/pyfpdb/SQL.py +++ b/pyfpdb/SQL.py @@ -1317,6 +1317,7 @@ class Sql: 1.25 would be a config value so user could change it) */ GROUP BY hc.PlayerId, hp.seatNo, p.name + ORDER BY hc.PlayerId, hp.seatNo, p.name """ # same as above except stats are aggregated for all blind/limit levels @@ -1418,6 +1419,7 @@ class Sql: ) ) GROUP BY hc.PlayerId, p.name + ORDER BY hc.PlayerId, p.name """ # NOTES on above cursor: # - Do NOT include %s inside query in a comment - the db api thinks diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index d5f4faec..79567578 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -60,7 +60,8 @@ if not options.errorsToConsole: errorFile = open('fpdb-error-log.txt', 'w', 0) sys.stderr = errorFile -import logging +#import logging +import logging, logging.config import pygtk pygtk.require('2.0') @@ -70,6 +71,7 @@ import interlocks import GuiPrefs +import GuiLogView import GuiBulkImport import GuiPlayerStats import GuiPositionalStats @@ -85,6 +87,8 @@ import Exceptions VERSION = "0.12" +log = Configuration.get_logger("logging.conf", "fpdb") + class fpdb: def tab_clicked(self, widget, tab_name): """called when a tab button is clicked to activate that tab""" @@ -334,27 +338,48 @@ class fpdb: diastring = "Please confirm that you want to re-create the HUD cache." self.dia_confirm.format_secondary_text(diastring) - hb = gtk.HBox(True, 1) + hb1 = gtk.HBox(True, 1) + self.h_start_date = gtk.Entry(max=12) + self.h_start_date.set_text( self.db.get_hero_hudcache_start() ) + lbl = gtk.Label(" Hero's cache starts: ") + btn = gtk.Button() + btn.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) + btn.connect('clicked', self.__calendar_dialog, self.h_start_date) + + hb1.pack_start(lbl, expand=True, padding=3) + hb1.pack_start(self.h_start_date, expand=True, padding=2) + hb1.pack_start(btn, expand=False, padding=3) + self.dia_confirm.vbox.add(hb1) + hb1.show_all() + + hb2 = gtk.HBox(True, 1) self.start_date = gtk.Entry(max=12) self.start_date.set_text( self.db.get_hero_hudcache_start() ) - lbl = gtk.Label(" Hero's cache starts: ") + lbl = gtk.Label(" Villains' cache starts: ") btn = gtk.Button() btn.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)) btn.connect('clicked', self.__calendar_dialog, self.start_date) - hb.pack_start(lbl, expand=True, padding=3) - hb.pack_start(self.start_date, expand=True, padding=2) - hb.pack_start(btn, expand=False, padding=3) - self.dia_confirm.vbox.add(hb) - hb.show_all() + hb2.pack_start(lbl, expand=True, padding=3) + hb2.pack_start(self.start_date, expand=True, padding=2) + hb2.pack_start(btn, expand=False, padding=3) + self.dia_confirm.vbox.add(hb2) + hb2.show_all() response = self.dia_confirm.run() - self.dia_confirm.destroy() if response == gtk.RESPONSE_YES: - self.db.rebuild_hudcache( self.start_date.get_text() ) + lbl = gtk.Label(" Rebuilding HUD Cache ... ") + self.dia_confirm.vbox.add(lbl) + lbl.show() + while gtk.events_pending(): + gtk.main_iteration_do(False) + + self.db.rebuild_hudcache( self.h_start_date.get_text(), self.start_date.get_text() ) elif response == gtk.RESPONSE_NO: print 'User cancelled rebuilding hud cache' + self.dia_confirm.destroy() + self.release_global_lock() def dia_rebuild_indexes(self, widget, data=None): @@ -368,16 +393,53 @@ class fpdb: self.dia_confirm.format_secondary_text(diastring) response = self.dia_confirm.run() - self.dia_confirm.destroy() if response == gtk.RESPONSE_YES: + lbl = gtk.Label(" Rebuilding Indexes ... ") + self.dia_confirm.vbox.add(lbl) + lbl.show() + while gtk.events_pending(): + gtk.main_iteration_do(False) self.db.rebuild_indexes() + + lbl.set_text(" Cleaning Database ... ") + while gtk.events_pending(): + gtk.main_iteration_do(False) self.db.vacuumDB() + + lbl.set_text(" Analyzing Database ... ") + while gtk.events_pending(): + gtk.main_iteration_do(False) self.db.analyzeDB() elif response == gtk.RESPONSE_NO: print 'User cancelled rebuilding db indexes' + self.dia_confirm.destroy() + self.release_global_lock() + def dia_logs(self, widget, data=None): + lock_set = False + if self.obtain_global_lock(): + lock_set = True + + 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() + + if lock_set: + self.release_global_lock() + + def addLogText(self, text): + end_iter = self.logbuffer.get_end_iter() + self.logbuffer.insert(end_iter, text) + self.logview.scroll_to_mark(self.logbuffer.get_insert(), 0) + def __calendar_dialog(self, widget, entry): self.dia_confirm.set_modal(False) d = gtk.Window(gtk.WINDOW_TOPLEVEL) @@ -397,10 +459,13 @@ class fpdb: d.show_all() def __get_dates(self): - t1 = self.start_date.get_text() + t1 = self.h_start_date.get_text() if t1 == '': t1 = '1970-01-01' - return (t1) + t2 = self.start_date.get_text() + if t2 == '': + t2 = '1970-01-01' + return (t1, t2) def __get_date(self, widget, calendar, entry, win): # year and day are correct, month is 0..11 @@ -477,6 +542,7 @@ class fpdb: + @@ -518,6 +584,7 @@ class fpdb: ('stats', None, '_Statistics (todo)', None, 'View Database Statistics', self.dia_database_stats), ('help', None, '_Help'), ('Abbrev', None, '_Abbrevations (todo)', None, 'List of Abbrevations', self.tab_abbreviations), + ('Logs', None, '_Log Messages', None, 'Log and Debug Messages', self.dia_logs), ('About', None, 'A_bout', None, 'About the program', self.dia_about), ('License', None, '_License and Copying (todo)', None, 'License and Copying', self.dia_licensing), ]) @@ -835,6 +902,7 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") gtk.main() return 0 + if __name__ == "__main__": me = fpdb() me.main() diff --git a/pyfpdb/logging.conf b/pyfpdb/logging.conf index 124b509d..1b54b453 100644 --- a/pyfpdb/logging.conf +++ b/pyfpdb/logging.conf @@ -11,6 +11,18 @@ keys=fileFormatter,stderrFormatter level=INFO handlers=consoleHandler,fileHandler +[logger_fpdb] +level=INFO +handlers=consoleHandler,fileHandler +qualname=fpdb +propagate=0 + +[logger_logview] +level=INFO +handlers=consoleHandler,fileHandler +qualname=logview +propagate=0 + [logger_parser] level=INFO handlers=consoleHandler,fileHandler @@ -24,7 +36,7 @@ qualname=importer propagate=0 [logger_config] -level=DEBUG +level=INFO handlers=consoleHandler,fileHandler qualname=config propagate=0 diff --git a/pyfpdb/test1.py b/pyfpdb/test1.py new file mode 100755 index 00000000..76bde20e --- /dev/null +++ b/pyfpdb/test1.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +"""test1.py + +Test if python is working. +""" +# Copyright 2008, 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 + +######################################################################## + +import sys + +print "\npython is working!" +print "\npress return to finish" + +sys.stdin.readline() diff --git a/pyfpdb/test2.py b/pyfpdb/test2.py new file mode 100755 index 00000000..c52d0a6c --- /dev/null +++ b/pyfpdb/test2.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +"""test2.py + +Test if gtk is working. +""" +# Copyright 2008, 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 + +######################################################################## + +import sys + +try: + import pygtk + pygtk.require('2.0') + import gtk + + + win = gtk.Window(gtk.WINDOW_TOPLEVEL) + win.set_title("Test GTK") + win.set_border_width(1) + win.set_default_size(600, 500) + win.set_resizable(True) + #win.show() + + dia = gtk.Dialog("Test GTK", + win, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CLOSE, gtk.RESPONSE_OK)) + dia.set_default_size(500, 300) + + l = gtk.Label("GTK is working!") + dia.vbox.add(l) + l.show() + + response = dia.run() + if response == gtk.RESPONSE_ACCEPT: + pass + dia.destroy() + +except: + print "\nError:", sys.exc_info() + print "\npress return to finish" + sys.stdin.readline()