diff --git a/pyfpdb/CarbonToFpdb.py b/pyfpdb/CarbonToFpdb.py
index cf8794da..b9d80791 100644
--- a/pyfpdb/CarbonToFpdb.py
+++ b/pyfpdb/CarbonToFpdb.py
@@ -133,8 +133,9 @@ or None if we fail to get the info """
self.info = {}
mg = m.groupdict()
+ print mg
- limits = { 'No Limit':'nl', 'Limit':'fl' }
+ limits = { 'No Limit':'nl', 'No Limit ':'nl', 'Limit':'fl' }
games = { # base, category
'Holdem' : ('hold','holdem'),
'Holdem Tournament' : ('hold','holdem') }
diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py
index 0647baf2..65e7a87d 100644
--- a/pyfpdb/Database.py
+++ b/pyfpdb/Database.py
@@ -55,6 +55,7 @@ import Card
import Charset
from Exceptions import *
import Configuration
+import Filters
# Other library modules
@@ -73,7 +74,7 @@ except ImportError:
use_numpy = False
-DB_VERSION = 144
+DB_VERSION = 145
# Variance created as sqlite has a bunch of undefined aggregate functions.
@@ -291,8 +292,8 @@ class Database:
# vars for hand ids or dates fetched according to above config:
self.hand_1day_ago = 0 # max hand id more than 24 hrs earlier than now
- self.date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD)
- self.h_date_ndays_ago = 'd000000' # date N days ago ('d' + YYMMDD) for hero
+ self.date_ndays_ago = 'd00000000' # date N days ago ('d' + YYMMDD)
+ self.h_date_ndays_ago = 'd00000000' # date N days ago ('d' + YYMMDD) for hero
self.date_nhands_ago = {} # dates N hands ago per player - not used yet
self.saveActions = False if self.import_options['saveActions'] == False else True
@@ -689,21 +690,23 @@ class Database:
else:
if row and row[0]:
self.hand_1day_ago = int(row[0])
-
- d = timedelta(days=hud_days)
+
+ offset = strptime(Filters.Filters(self, self.config, self.sql).getDates()[0],"%Y-%m-%d %H:%M:%S").tm_hour
+
+ d = timedelta(days=hud_days, hours=offset)
now = datetime.utcnow() - d
- self.date_ndays_ago = "d%02d%02d%02d" % (now.year - 2000, now.month, now.day)
+ self.date_ndays_ago = "d%02d%02d%02d%02d" % (now.year - 2000, now.month, now.day, offset)
- d = timedelta(days=h_hud_days)
+ d = timedelta(days=h_hud_days, hours=offset)
now = datetime.utcnow() - d
- self.h_date_ndays_ago = "d%02d%02d%02d" % (now.year - 2000, now.month, now.day)
+ self.h_date_ndays_ago = "d%02d%02d%02d%02d" % (now.year - 2000, now.month, now.day, offset)
def init_player_hud_stat_vars(self, playerid):
# not sure if this is workable, to be continued ...
try:
# self.date_nhands_ago is used for fetching stats for last n hands (hud_style = 'H')
# This option not used yet - needs to be called for each player :-(
- self.date_nhands_ago[str(playerid)] = 'd000000'
+ self.date_nhands_ago[str(playerid)] = 'd00000000'
# should use aggregated version of query if appropriate
c.execute(self.sql.query['get_date_nhands_ago'], (self.hud_hands, playerid))
@@ -771,11 +774,11 @@ class Database:
if hud_style == 'T':
stylekey = self.date_ndays_ago
elif hud_style == 'A':
- stylekey = '0000000' # all stylekey values should be higher than this
+ stylekey = '000000000' # all stylekey values should be higher than this
elif hud_style == 'S':
- stylekey = 'zzzzzzz' # all stylekey values should be lower than this
+ stylekey = 'zzzzzzzzz' # all stylekey values should be lower than this
else:
- stylekey = '0000000'
+ stylekey = '000000000'
log.info('hud_style: %s' % hud_style)
#elif hud_style == 'H':
@@ -784,11 +787,11 @@ class Database:
if h_hud_style == 'T':
h_stylekey = self.h_date_ndays_ago
elif h_hud_style == 'A':
- h_stylekey = '0000000' # all stylekey values should be higher than this
+ h_stylekey = '000000000' # all stylekey values should be higher than this
elif h_hud_style == 'S':
- h_stylekey = 'zzzzzzz' # all stylekey values should be lower than this
+ h_stylekey = 'zzzzzzzzz' # all stylekey values should be lower than this
else:
- h_stylekey = '000000'
+ h_stylekey = '00000000'
log.info('h_hud_style: %s' % h_hud_style)
#elif h_hud_style == 'H':
@@ -1824,11 +1827,11 @@ class Database:
"""Update cached statistics. If update fails because no record exists, do an insert."""
if self.use_date_in_hudcache:
- styleKey = datetime.strftime(starttime, 'd%y%m%d')
- #styleKey = "d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day)
+ styleKey = datetime.strftime(starttime, 'd%y%m%d%H')
+ #styleKey = "d%02d%02d%02d%02d" % (hand_start_time.year-2000, hand_start_time.month, hand_start_time.day, hand_start_time.hour)
else:
- # hard-code styleKey as 'A000000' (all-time cache, no key) for now
- styleKey = 'A000000'
+ # hard-code styleKey as 'A00000000' (all-time cache, no key) for now
+ styleKey = 'A00000000'
update_hudcache = self.sql.query['update_hudcache']
update_hudcache = update_hudcache.replace('%s', self.sql.query['placeholder'])
diff --git a/pyfpdb/GuiSessionViewer.py b/pyfpdb/GuiSessionViewer.py
index d12f1d10..6f0fd269 100644
--- a/pyfpdb/GuiSessionViewer.py
+++ b/pyfpdb/GuiSessionViewer.py
@@ -50,11 +50,14 @@ import Database
import Filters
import Charset
+DEBUG = False
+
class GuiSessionViewer (threading.Thread):
def __init__(self, config, querylist, mainwin, debug=True):
self.debug = debug
self.conf = config
self.sql = querylist
+ self.window = mainwin
self.liststore = None
@@ -153,6 +156,28 @@ class GuiSessionViewer (threading.Thread):
# make sure Hand column is not displayed
#[x for x in self.columns if x[0] == 'hand'][0][1] = False
+ if DEBUG == False:
+ warning_string = """
+Session Viewer is proof of concept code only, and contains many bugs.
+
+Feel free to use the viewer, but there is no guarantee that the data is accurate.
+
+If you are interested in developing the code further please contact us via the usual channels.
+
+Thankyou
+"""
+ self.warning_box(warning_string)
+
+ def warning_box(self, str, diatitle=_("FPDB WARNING")):
+ diaWarning = gtk.Dialog(title=diatitle, parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
+
+ label = gtk.Label(str)
+ diaWarning.vbox.add(label)
+ label.show()
+
+ response = diaWarning.run()
+ diaWarning.destroy()
+ return response
def get_vbox(self):
"""returns the vbox of this thread"""
diff --git a/pyfpdb/GuiStove.py b/pyfpdb/GuiStove.py
new file mode 100644
index 00000000..1ee825e2
--- /dev/null
+++ b/pyfpdb/GuiStove.py
@@ -0,0 +1,205 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#Copyright 2008-2010 Steffen Schaumburg
+#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.
+
+import L10n
+_ = L10n.get_translation()
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os
+import sys
+
+import Charset
+
+DEBUG = False
+
+class GuiStove():
+
+ def __init__(self, config, parent, debug=True):
+ """Constructor for GraphViewer"""
+ self.conf = config
+ self.parent = parent
+
+ self.mainHBox = gtk.HBox(False, 0)
+
+ # hierarchy: self.mainHBox / self.notebook
+
+ self.notebook = gtk.Notebook()
+ self.notebook.set_tab_pos(gtk.POS_TOP)
+ self.notebook.set_show_tabs(True)
+ self.notebook.set_show_border(True)
+
+ self.createFlopTab()
+ #self.createStudTab()
+ #self.createDrawTab()
+
+
+ self.mainHBox.add(self.notebook)
+
+ self.mainHBox.show_all()
+
+ if DEBUG == False:
+ warning_string = """
+Stove is a GUI mockup of a EV calculation page, and completely non functional.
+
+Unless you are interested in developing this feature, please ignore this page.
+
+If you are interested in developing the code further see GuiStove.py and Stove.py
+
+Thankyou
+"""
+ self.warning_box(warning_string)
+
+
+ def warning_box(self, str, diatitle=_("FPDB WARNING")):
+ diaWarning = gtk.Dialog(title=diatitle, parent=self.parent, flags=gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
+
+ label = gtk.Label(str)
+ diaWarning.vbox.add(label)
+ label.show()
+
+ response = diaWarning.run()
+ diaWarning.destroy()
+ return response
+
+
+ def get_active_text(combobox):
+ model = combobox.get_model()
+ active = combobox.get_active()
+ if active < 0:
+ return None
+ return model[active][0]
+
+ def create_combo_box(self, strings):
+ combobox = gtk.combo_box_new_text()
+ for label in strings:
+ combobox.append_text(label)
+ combobox.set_active(0)
+ return combobox
+
+
+ def createFlopTab(self):
+ # hierarchy: hbox / ddbox / ddhbox / Label + flop_games_cb | label + players_cb
+ # / gamehbox / in_frame / table /
+ # / out_frame
+
+ tab_title = "Flop"
+ label = gtk.Label(tab_title)
+
+ ddbox = gtk.VBox(False, 0)
+ self.notebook.append_page(ddbox, label)
+
+ ddhbox = gtk.HBox(False, 0)
+ gamehbox = gtk.HBox(False, 0)
+
+ ddbox.add(ddhbox)
+ ddbox.add(gamehbox)
+
+ # Combo boxes in the top row
+
+ games = [ "Holdem", "Omaha", "Omaha 8", ]
+ players = [ "2", "3", "4", "5", "6", "7", "8", "9", "10" ]
+ flop_games_cb = self.create_combo_box(games)
+ players_cb = self.create_combo_box(players)
+
+ label = gtk.Label("Gametype:")
+ ddhbox.add(label)
+ ddhbox.add(flop_games_cb)
+ label = gtk.Label("Players:")
+ ddhbox.add(label)
+ ddhbox.add(players_cb)
+
+ # Frames for Stove input and output
+
+ in_frame = gtk.Frame("Input:")
+ out_frame = gtk.Frame("Output:")
+
+ gamehbox.add(in_frame)
+ gamehbox.add(out_frame)
+
+ outstring = """
+No board given. Using Monte-Carlo simulation...
+Enumerated 2053443 possible plays.
+Your hand: (Ad Ac)
+Against the range: {
+ AhAd, AhAs, AdAs, KhKd, KhKs,
+ KhKc, KdKs, KdKc, KsKc, QhQd,
+ QhQs, QhQc, QdQs, QdQc, QsQc,
+ JhJd, JhJs, JhJc, JdJs, JdJc,
+ JsJc
+ }
+
+ Win Lose Tie
+ 69.91% 15.83% 14.26%
+
+"""
+ label = gtk.Label(outstring)
+ out_frame.add(label)
+
+ # Input Frame
+ table = gtk.Table(4, 4, True)
+ label = gtk.Label("Board:")
+ board = gtk.Entry()
+ #board.connect("changed", self._some_function, arg)
+
+ btn1 = gtk.Button()
+ btn1.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
+ #btn.connect('clicked', self._some_function, arg)
+ table.attach(label, 0, 1, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(board, 1, 2, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(btn1, 2, 3, 0, 1, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+
+
+ label = gtk.Label("Player1:")
+ board = gtk.Entry()
+ #board.connect("changed", self._some_function, arg)
+ btn2 = gtk.Button()
+ btn2.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
+ #btn.connect('clicked', self._some_function, arg)
+ btn3 = gtk.Button()
+ btn3.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
+ #btn.connect('clicked', self._some_function, arg)
+ table.attach(label, 0, 1, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(board, 1, 2, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(btn2, 2, 3, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(btn3, 3, 4, 1, 2, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+
+
+ label = gtk.Label("Player2:")
+ board = gtk.Entry()
+ #board.connect("changed", self._some_function, arg)
+ btn4 = gtk.Button()
+ btn4.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
+ #btn.connect('clicked', self._some_function, arg)
+ btn5 = gtk.Button()
+ btn5.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
+ #btn.connect('clicked', self._some_function, arg)
+ table.attach(label, 0, 1, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(board, 1, 2, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(btn4, 2, 3, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+ table.attach(btn5, 3, 4, 2, 3, xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)
+
+ #table.attach(label, i, i+1, j, j+1,)
+ in_frame.add(table)
+
+
+
+ def get_vbox(self):
+ """returns the vbox of this thread"""
+ return self.mainHBox
+ #end def get_vbox
diff --git a/pyfpdb/HUD_main.pyw b/pyfpdb/HUD_main.pyw
old mode 100644
new mode 100755
index cd29f4ae..145e9048
--- a/pyfpdb/HUD_main.pyw
+++ b/pyfpdb/HUD_main.pyw
@@ -24,9 +24,6 @@
Main for FreePokerTools HUD.
"""
# TODO allow window resizing
-# TODO hud to echo, but ignore non numbers
-# TODO no stat window for hero
-# TODO things to add to config.xml
# Standard Library modules
import sys
@@ -39,26 +36,21 @@ import traceback
import thread
import time
import string
-import re
# pyGTK modules
-import pygtk
import gtk
import gobject
# FreePokerTools modules
import Configuration
-
-
import Database
-from HandHistoryConverter import getTableTitleRe
+import Hud
+
# get the correct module for the current os
if os.name == 'posix':
import XTables as Tables
elif os.name == 'nt':
import WinTables as Tables
-#import Tables
-import Hud
import locale
lang = locale.getdefaultlocale()[0][0:2]
@@ -79,7 +71,6 @@ else:
c = Configuration.Config(file=options.config, dbname=options.dbname)
log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt')
-
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.
@@ -144,14 +135,13 @@ class HUD_main(object):
pass
def client_destroyed(self, widget, hud): # call back for terminating the main eventloop
- self.kill_hud(None, hud.table.name)
+ self.kill_hud(None, hud.table.key)
def game_changed(self, widget, hud):
print _("hud_main: Game changed.")
def table_changed(self, widget, hud):
- print _("hud_main: Table changed.")
- self.kill_hud(None, hud.table.name)
+ self.kill_hud(None, hud.table.key)
def destroy(self, *args): # call back for terminating the main eventloop
log.info(_("Terminating normally."))
@@ -159,12 +149,24 @@ class HUD_main(object):
def kill_hud(self, event, table):
# called by an event in the HUD, to kill this specific HUD
- if table in self.hud_dict:
- self.hud_dict[table].kill()
- self.hud_dict[table].main_window.destroy()
- self.vb.remove(self.hud_dict[table].tablehudlabel)
- del(self.hud_dict[table])
- self.main_window.resize(1, 1)
+
+# This method can be called by either gui or non-gui thread. It doesn't
+# cost much to always do it in a thread-safe manner.
+ def idle():
+ gtk.gdk.threads_enter()
+ try:
+ if table in self.hud_dict:
+ self.hud_dict[table].kill()
+ self.hud_dict[table].main_window.destroy()
+ self.vb.remove(self.hud_dict[table].tablehudlabel)
+ del(self.hud_dict[table])
+ self.main_window.resize(1, 1)
+ except:
+ pass
+ finally:
+ gtk.gdk.threads_leave()
+
+ gobject.idle_add(idle)
def check_tables(self):
for hud in self.hud_dict.keys():
@@ -178,42 +180,42 @@ class HUD_main(object):
gtk.gdk.threads_enter()
try:
- 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:
+
+ self.hud_dict[table.key].tablehudlabel = newlabel
+ self.hud_dict[table.key].create(new_hand_id, self.config, stat_dict, cards)
+ for m in self.hud_dict[table.key].aux_windows:
m.create()
m.update_gui(new_hand_id)
- self.hud_dict[table_name].update(new_hand_id, self.config)
- self.hud_dict[table_name].reposition_windows()
+ self.hud_dict[table.key].update(new_hand_id, self.config)
+ self.hud_dict[table.key].reposition_windows()
except:
log.error("*** Exception in HUD_main::idle_func() *** " + str(sys.exc_info()))
for e in traceback.format_tb(sys.exc_info()[2]):
log.error(e)
finally:
gtk.gdk.threads_leave()
- return False
-
- self.hud_dict[table_name] = Hud.Hud(self, table, max, poker_game, self.config, self.db_connection)
- self.hud_dict[table_name].table_name = table_name
- self.hud_dict[table_name].stat_dict = stat_dict
- self.hud_dict[table_name].cards = cards
+ return False
+ self.hud_dict[table.key] = Hud.Hud(self, table, max, poker_game, self.config, self.db_connection)
+ self.hud_dict[table.key].table_name = table_name
+ self.hud_dict[table.key].stat_dict = stat_dict
+ self.hud_dict[table.key].cards = cards
+ table.hud = self.hud_dict[table.key]
+
# set agg_bb_mult so that aggregate_tour and aggregate_ring can be ignored,
# agg_bb_mult == 1 means no aggregation after these if statements:
if type == "tour" and self.hud_params['aggregate_tour'] == False:
- self.hud_dict[table_name].hud_params['agg_bb_mult'] = 1
+ self.hud_dict[table.key].hud_params['agg_bb_mult'] = 1
elif type == "ring" and self.hud_params['aggregate_ring'] == False:
- self.hud_dict[table_name].hud_params['agg_bb_mult'] = 1
+ self.hud_dict[table.key].hud_params['agg_bb_mult'] = 1
if type == "tour" and self.hud_params['h_aggregate_tour'] == False:
- self.hud_dict[table_name].hud_params['h_agg_bb_mult'] = 1
+ self.hud_dict[table.key].hud_params['h_agg_bb_mult'] = 1
elif type == "ring" and self.hud_params['h_aggregate_ring'] == False:
- self.hud_dict[table_name].hud_params['h_agg_bb_mult'] = 1
+ self.hud_dict[table.key].hud_params['h_agg_bb_mult'] = 1
# sqlcoder: I forget why these are set to true (aren't they ignored from now on?)
# but I think it's needed:
self.hud_params['aggregate_ring'] = True
@@ -222,7 +224,7 @@ class HUD_main(object):
self.hud_params['aggregate_tour'] = True
self.hud_params['h_aggregate_tour'] = True
- [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table_name].aux_windows]
+ [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[table.key].aux_windows]
gobject.idle_add(idle_func)
def update_HUD(self, new_hand_id, table_name, config):
@@ -268,6 +270,8 @@ class HUD_main(object):
self.destroy()
break # this thread is not always killed immediately with gtk.main_quit()
+# This block cannot be hoisted outside the while loop, because it would
+# cause a problem when auto importing into an empty db.
if not found:
for site in self.config.get_supported_sites():
result = self.db_connection.get_site_id(site)
@@ -355,9 +359,10 @@ class HUD_main(object):
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 type == "tour":
-# tablewindow.check_table_no(None)
-# # Ray!! tablewindow::check_table_no expects a HUD as an argument!
+
+ if type == "tour":
+ self.hud_dict[temp_key].table.check_table_no(self.hud_dict[temp_key])
+
if __name__== "__main__":
# start the HUD_main object
diff --git a/pyfpdb/IdentifySite.py b/pyfpdb/IdentifySite.py
index b4f1a8a7..723991f3 100644
--- a/pyfpdb/IdentifySite.py
+++ b/pyfpdb/IdentifySite.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Copyright 2010 Chaz Littlejohn
@@ -28,18 +28,21 @@ import Configuration
import Database
__ARCHIVE_PRE_HEADER_REGEX='^Hand #(\d+)\s*$|\*{20}\s#\s\d+\s\*+\s+'
-re_SplitArchive = re.compile(__ARCHIVE_PRE_HEADER_REGEX)
+re_SplitArchive = re.compile(__ARCHIVE_PRE_HEADER_REGEX, re.MULTILINE)
class IdentifySite:
def __init__(self, config, in_path = '-'):
self.in_path = in_path
self.config = config
- self.db = Database.Database(config)
+ self.db = Database.Database(self.config)
self.sitelist = {}
self.filelist = {}
self.generateSiteList()
- self.walkDirectory(self.in_path, self.sitelist)
+ if os.path.isdir(self.in_path):
+ self.walkDirectory(self.in_path, self.sitelist)
+ else:
+ self.idSite(self.in_path, self.sitelist)
def generateSiteList(self):
"""Generates a ordered dictionary of site, filter and filter name for each site in hhcs"""
@@ -80,7 +83,7 @@ class IdentifySite:
for kodec in self.__listof(obj.codepage):
try:
in_fh = codecs.open(file, 'r', kodec)
- whole_file = in_fh.read()
+ whole_file = in_fh.read(2000)
in_fh.close()
if info[2] in ('OnGame', 'Winamax'):
@@ -94,7 +97,7 @@ class IdentifySite:
if re_SplitArchive.search(whole_file):
archive = True
if m:
- self.filelist[file] = [info[0]] + [info[1]] + [kodec] + [archive]
+ self.filelist[file] = [info[1]] + [kodec] + [archive]
break
except:
pass
diff --git a/pyfpdb/Mucked.py b/pyfpdb/Mucked.py
index cda24631..cf948221 100755
--- a/pyfpdb/Mucked.py
+++ b/pyfpdb/Mucked.py
@@ -415,9 +415,12 @@ class Aux_Seats(Aux_Window):
# Methods likely to be of use for any Seat_Window implementation
def destroy(self):
"""Destroy all of the seat windows."""
- for i in self.m_windows.keys():
- self.m_windows[i].destroy()
- del(self.m_windows[i])
+ try:
+ for i in self.m_windows.keys():
+ self.m_windows[i].destroy()
+ del(self.m_windows[i])
+ except AttributeError:
+ pass
# Methods likely to be useful for mucked card windows (or similar) only
def hide(self):
diff --git a/pyfpdb/Options.py b/pyfpdb/Options.py
index b8be3d2a..6369b485 100644
--- a/pyfpdb/Options.py
+++ b/pyfpdb/Options.py
@@ -59,6 +59,8 @@ def fpdb_options():
help=_("File to be split is a PokerStars or Full Tilt Poker archive file"))
parser.add_option("-n", "--numhands", dest="hands", default="100", type="int",
help=_("How many hands do you want saved to each file. Default is 100"))
+ parser.add_option("-w", "--workerid", dest="workerid", default="0", type="int",
+ help=_("Specifies the worker id running the script"))
(options, argv) = parser.parse_args()
diff --git a/pyfpdb/PokerStarsToFpdb.py b/pyfpdb/PokerStarsToFpdb.py
index 16c4091e..3817ae3f 100644
--- a/pyfpdb/PokerStarsToFpdb.py
+++ b/pyfpdb/PokerStarsToFpdb.py
@@ -298,7 +298,7 @@ class PokerStars(HandHistoryConverter):
if key == 'BUTTON':
hand.buttonpos = info[key]
if key == 'MAX':
- hand.maxseats = int(info[key])
+ if info[key]: hand.maxseats = int(info[key])
if key == 'MIXED':
hand.mixed = self.mixes[info[key]] if info[key] is not None else None
diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py
index d45ccd38..6a505eae 100644
--- a/pyfpdb/SQL.py
+++ b/pyfpdb/SQL.py
@@ -1064,7 +1064,7 @@ class Sql:
activeSeats SMALLINT NOT NULL,
position CHAR(1),
tourneyTypeId SMALLINT UNSIGNED, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
- styleKey CHAR(7) NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */
+ styleKey CHAR(9) NOT NULL, /* 1st char is style (A/T/H/S), other 8 are the key */
HDs INT NOT NULL,
wonWhenSeenStreet1 FLOAT,
@@ -1165,7 +1165,7 @@ class Sql:
activeSeats SMALLINT,
position CHAR(1),
tourneyTypeId INT, FOREIGN KEY (tourneyTypeId) REFERENCES TourneyTypes(id),
- styleKey CHAR(7) NOT NULL, /* 1st char is style (A/T/H/S), other 6 are the key */
+ styleKey CHAR(9) NOT NULL, /* 1st char is style (A/T/H/S), other 8 are the key */
HDs INT,
wonWhenSeenStreet1 FLOAT,
@@ -2047,7 +2047,7 @@ class Sql:
# gets a date, would need to use handsplayers (not hudcache) to get exact hand Id
if db_server == 'mysql':
self.query['get_date_nhands_ago'] = """
- select concat( 'd', date_format(max(h.startTime), '%Y%m%d') )
+ select concat( 'd', date_format(max(h.startTime), '%Y%m%d%H') )
from (select hp.playerId
,coalesce(greatest(max(hp.handId)-%s,1),1) as maxminusx
from HandsPlayers hp
@@ -2059,7 +2059,7 @@ class Sql:
"""
elif db_server == 'postgresql':
self.query['get_date_nhands_ago'] = """
- select 'd' || to_char(max(h3.startTime), 'YYMMDD')
+ select 'd' || to_char(max(h3.startTime), 'YYMMDDHH')
from (select hp.playerId
,coalesce(greatest(max(hp.handId)-%s,1),1) as maxminusx
from HandsPlayers hp
@@ -2071,7 +2071,7 @@ class Sql:
"""
elif db_server == 'sqlite': # untested guess at query:
self.query['get_date_nhands_ago'] = """
- select 'd' || strftime(max(h3.startTime), 'YYMMDD')
+ select 'd' || strftime(max(h3.startTime), 'YYMMDDHH')
from (select hp.playerId
,coalesce(greatest(max(hp.handId)-%s,1),1) as maxminusx
from HandsPlayers hp
@@ -3290,7 +3290,7 @@ class Sql:
else 'E'
end AS hc_position
- ,date_format(h.startTime, 'd%y%m%d')
+ ,date_format(h.startTime, 'd%y%m%d%H')
,count(1)
,sum(wonWhenSeenStreet1)
,sum(wonWhenSeenStreet2)
@@ -3379,7 +3379,7 @@ class Sql:
,h.seats
,hc_position
- ,date_format(h.startTime, 'd%y%m%d')
+ ,date_format(h.startTime, 'd%y%m%d%H')
"""
elif db_server == 'postgresql':
self.query['rebuildHudCache'] = """
@@ -3488,7 +3488,7 @@ class Sql:
else 'E'
end AS hc_position
- ,'d' || to_char(h.startTime, 'YYMMDD')
+ ,'d' || to_char(h.startTime, 'YYMMDDHH')
,count(1)
,sum(wonWhenSeenStreet1)
,sum(wonWhenSeenStreet2)
@@ -3577,7 +3577,7 @@ class Sql:
,h.seats
,hc_position
- ,to_char(h.startTime, 'YYMMDD')
+ ,to_char(h.startTime, 'YYMMDDHH')
"""
else: # assume sqlite
self.query['rebuildHudCache'] = """
@@ -3686,7 +3686,7 @@ class Sql:
else 'E'
end AS hc_position
- ,'d' || substr(strftime('%Y%m%d', h.startTime),3,7)
+ ,'d' || substr(strftime('%Y%m%d%H', h.startTime),3,9)
,count(1)
,sum(wonWhenSeenStreet1)
,sum(wonWhenSeenStreet2)
@@ -3775,7 +3775,7 @@ class Sql:
,h.seats
,hc_position
- ,'d' || substr(strftime('%Y%m%d', h.startTime),3,7)
+ ,'d' || substr(strftime('%Y%m%d%H', h.startTime),3,9)
"""
self.query['insert_hudcache'] = """
diff --git a/pyfpdb/SplitHandHistory.py b/pyfpdb/SplitHandHistory.py
index 52c1d340..c996f903 100644
--- a/pyfpdb/SplitHandHistory.py
+++ b/pyfpdb/SplitHandHistory.py
@@ -1,19 +1,22 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python
-#Copyright 2010 Chaz Littlejohn
-#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.
+# Copyright 2010, Chaz Littlejohn
+#
+# 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 L10n
_ = L10n.get_translation()
@@ -28,6 +31,7 @@ import Options
import Configuration
from Exceptions import *
from cStringIO import StringIO
+import time
(options, argv) = Options.fpdb_options()
@@ -37,7 +41,7 @@ codepage = ["utf-16", "utf-8", "cp1252"]
class SplitHandHistory:
- def __init__(self, config, in_path = '-', out_path = None, hands = 100, filter = "PokerStarsToFpdb", archive = False):
+ def __init__(self, config, in_path = '-', out_path = None, hands = 100, filter = "PokerStarsToFpdb", archive = False, workerid = 0):
self.config = config
self.in_path = in_path
self.out_path = out_path
@@ -50,22 +54,25 @@ class SplitHandHistory:
self.line_addendum = None
self.filedone = False
+ self.timestamp = str(time.time())
+ self.workerid = '%02d' % workerid
+
#Acquire re_SplitHands for this hh
- filter_name = filter.replace("ToFpdb", "")
+ self.filter_name = filter.replace("ToFpdb", "")
mod = __import__(filter)
- obj = getattr(mod, filter_name, None)
+ obj = getattr(mod, self.filter_name, None)
self.re_SplitHands = obj.re_SplitHands
#Determine line delimiter type if any
- if self.re_SplitHands.match('\n\n'):
- self.line_delimiter = '\n\n'
if self.re_SplitHands.match('\n\n\n'):
self.line_delimiter = '\n\n\n'
+ if self.re_SplitHands.match('\n\n'):
+ self.line_delimiter = '\n\n'
#Add new line addendum for sites which match SplitHand to next line as well
- if filter_name == 'OnGame':
+ if self.filter_name == 'OnGame':
self.line_addendum = '*'
- if filter_name == 'Carbon':
+ if self.filter_name == 'Carbon':
self.line_addendum = ' %s' % name
newfile = file(name, 'w')
+ os.chmod(name, 0775)
return newfile
#Archive Hand Splitter
@@ -122,8 +131,11 @@ class SplitHandHistory:
except FpdbEndOfFile:
done = True
break
+ except UnicodeEncodeError:
+ print _('Absurd character done messed you up')
+ sys.exit(2)
except:
- print _("Unexpected error processing file")
+ print _('Unexpected error processing file')
sys.exit(2)
n += 1
outfile.close()
@@ -174,7 +186,7 @@ class SplitHandHistory:
l = infile.readline()
l = l.replace('\r\n', '\n')
outfile.write(l)
- l = infile.readline()
+ l = infile.readline().encode(self.kodec)
while len(l) < 3:
l = infile.readline()
@@ -182,7 +194,7 @@ class SplitHandHistory:
while len(l) > 2:
l = l.replace('\r\n', '\n')
outfile.write(l)
- l = infile.readline()
+ l = infile.readline().encode(self.kodec)
outfile.write(self.line_delimiter)
return infile
@@ -195,13 +207,19 @@ class SplitHandHistory:
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
+
+ if not options.filename:
+ options.filename = sys.argv[1]
if not options.config:
- options.config = Configuration.Config(file = "HUD_config.test.xml")
-
+ options.config = sys.argv[2]
+
+ if sys.argv[3] == "True":
+ options.archive = True
+
if options.filename:
SplitHH = SplitHandHistory(options.config, options.filename, options.outpath, options.hands,
- options.hhc, options.archive)
+ options.hhc, options.archive, options.workerid)
if __name__ == '__main__':
sys.exit(main())
diff --git a/pyfpdb/Stove.py b/pyfpdb/Stove.py
index ffbdff4f..eb939c0f 100755
--- a/pyfpdb/Stove.py
+++ b/pyfpdb/Stove.py
@@ -9,6 +9,7 @@
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
+#TODO: gettextify
import sys, random
import pokereval
diff --git a/pyfpdb/TableWindow.py b/pyfpdb/TableWindow.py
index d780bdbc..61d7bd80 100644
--- a/pyfpdb/TableWindow.py
+++ b/pyfpdb/TableWindow.py
@@ -116,6 +116,7 @@ class Table_Window(object):
self.config = config
self.site = site
+ self.hud = None # fill in later
if tournament is not None and table_number is not None:
self.tournament = int(tournament)
self.table = int(table_number)
@@ -123,11 +124,13 @@ class Table_Window(object):
self.type = "tour"
table_kwargs = dict(tournament = self.tournament, table_number = self.table)
self.tableno_re = getTableNoRe(self.config, self.site, tournament = self.tournament)
+ self.key = tournament # used as key for the hud_dict in HUD_main
elif table_name is not None:
self.name = table_name
self.type = "cash"
self.tournament = None
table_kwargs = dict(table_name = table_name)
+ self.key = table_name
else:
return None
@@ -135,6 +138,7 @@ class Table_Window(object):
self.search_string = getTableTitleRe(self.config, self.site, self.type, **table_kwargs)
self.find_table_parameters()
+ self.gdkhandle = gtk.gdk.window_foreign_new(self.number)
geo = self.get_geometry()
if geo is None: return None
self.width = geo['width']
@@ -150,7 +154,7 @@ class Table_Window(object):
# __str__ method for testing
likely_attrs = ("number", "title", "site", "width", "height", "x", "y",
"tournament", "table", "gdkhandle", "window", "parent",
- "game", "search_string", "tableno_re")
+ "key", "hud", "game", "search_string", "tableno_re")
temp = 'TableWindow object\n'
for a in likely_attrs:
if getattr(self, a, 0):
@@ -185,13 +189,13 @@ class Table_Window(object):
return False
try:
- mo = re.search(self.tableno_re, new_title)
+ mo = re.search(self.tableno_re, new_title)
except AttributeError: #'Table' object has no attribute 'tableno_re'
- return False
+ return False
if mo is not None:
#print "get_table_no: mo=",mo.groups()
- return mo.group(1)
+ return int(mo.group(1))
return False
####################################################################
@@ -256,7 +260,7 @@ class Table_Window(object):
if result != False and result != self.table:
self.table = result
if hud is not None:
- hud.main_window.emit("table_changed", hud)
+ hud.parent.main_window.emit("table_changed", hud)
return True
def check_bad_words(self, title):
diff --git a/pyfpdb/TestHandsPlayers.py b/pyfpdb/TestHandsPlayers.py
index ee3be1e9..e0013ecc 100755
--- a/pyfpdb/TestHandsPlayers.py
+++ b/pyfpdb/TestHandsPlayers.py
@@ -201,6 +201,7 @@ def main(argv=None):
walk_testfiles("regression-test-files/cash/iPoker/", compare, importer, iPokerErrors, "iPoker")
if sites['Winamax'] == True:
walk_testfiles("regression-test-files/cash/Winamax/", compare, importer, WinamaxErrors, "Winamax")
+ walk_testfiles("regression-test-files/tour/Winamax/", compare, importer, WinamaxErrors, "Winamax")
if sites['Win2day'] == True:
walk_testfiles("regression-test-files/cash/Win2day/", compare, importer, Win2dayErrors, "Win2day")
diff --git a/pyfpdb/WinamaxToFpdb.py b/pyfpdb/WinamaxToFpdb.py
index c49f4e09..47721515 100644
--- a/pyfpdb/WinamaxToFpdb.py
+++ b/pyfpdb/WinamaxToFpdb.py
@@ -82,15 +82,26 @@ class Winamax(HandHistoryConverter):
# Winamax Poker - CashGame - HandId: #279823-223-1285031451 - Holdem no limit (0.02€/0.05€) - 2010/09/21 03:10:51 UTC
# Table: 'Charenton-le-Pont' 9-max (real money) Seat #5 is the button
re_HandInfo = re.compile(u"""
- \s*Winamax\sPoker\s-\sCashGame\s-\sHandId:\s\#(?P\d+)-(?P\d+)-(?P\d+).*\s
+ \s*Winamax\sPoker\s-\s
+ (?PCashGame)?
+ (?PTournament\s
+ (?P.+)?\s
+ buyIn:\s(?P(?P[%(LS)s\d\,]+)?\s\+?\s(?P[%(LS)s\d\,]+)?\+?(?P[%(LS)s\d\.]+)?\s?(?P%(LEGAL_ISO)s)?|Gratuit|Ticket\suniquement)?\s
+ (level:\s(?P\d+))?
+ .*)?
+ \s-\sHandId:\s\#(?P\d+)-(?P\d+)-(?P\d+).*\s
(?PHoldem|Omaha)\s
(?Pno\slimit|pot\slimit)\s
\(
+ (((%(LS)s)?(?P[.0-9]+)(%(LS)s)?)/)?
((%(LS)s)?(?P[.0-9]+)(%(LS)s)?)/
((%(LS)s)?(?P[.0-9]+)(%(LS)s)?)
\)\s-\s
(?P.*)
- Table:\s\'(?P[^']+)\'\s(?P\d+)\-max
+ Table:\s\'(?P[^(]+)
+ (.(?P\d+).\#(?P\d+))?.*
+ \'
+ \s(?P\d+)\-max
""" % substitutions, re.MULTILINE|re.DOTALL|re.VERBOSE)
re_TailSplitHands = re.compile(r'\n\s*\n')
@@ -126,8 +137,8 @@ class Winamax(HandHistoryConverter):
self.re_PostSB = re.compile('%(PLYR)s posts small blind (%(CUR)s)?(?P[\.0-9]+)(%(CUR)s)?' % subst, re.MULTILINE)
self.re_PostBB = re.compile('%(PLYR)s posts big blind (%(CUR)s)?(?P[\.0-9]+)(%(CUR)s)?' % subst, re.MULTILINE)
self.re_DenySB = re.compile('(?P.*) deny SB' % subst, re.MULTILINE)
- self.re_Antes = re.compile(r"^%(PLYR)s: posts the ante (%(CUR)s)?(?P[\.0-9]+)(%(CUR)s)?" % subst, re.MULTILINE)
- self.re_BringIn = re.compile(r"^%(PLYR)s: brings[- ]in( low|) for (%(CUR)s)?(?P[\.0-9]+(%(CUR)s)?)" % subst, re.MULTILINE)
+ self.re_Antes = re.compile(r"^%(PLYR)s posts ante (%(CUR)s)?(?P[\.0-9]+)(%(CUR)s)?" % subst, re.MULTILINE)
+ self.re_BringIn = re.compile(r"^%(PLYR)s brings[- ]in( low|) for (%(CUR)s)?(?P[\.0-9]+(%(CUR)s)?)" % subst, re.MULTILINE)
self.re_PostBoth = re.compile('(?P.*): posts small \& big blind \( (%(CUR)s)?(?P[\.0-9]+)(%(CUR)s)?\)' % subst)
self.re_PostDead = re.compile('(?P.*) posts dead blind \((%(CUR)s)?(?P[\.0-9]+)(%(CUR)s)?\)' % subst, re.MULTILINE)
self.re_HeroCards = re.compile('Dealt\sto\s%(PLYR)s\s\[(?P.*)\]' % subst)
@@ -144,6 +155,9 @@ class Winamax(HandHistoryConverter):
["ring", "hold", "fl"],
["ring", "hold", "nl"],
["ring", "hold", "pl"],
+ ["tour", "hold", "fl"],
+ ["tour", "hold", "nl"],
+ ["tour", "hold", "pl"],
]
def determineGameType(self, handText):
@@ -160,7 +174,11 @@ class Winamax(HandHistoryConverter):
mg = m.groupdict()
- info['type'] = 'ring'
+ if mg.get('TOUR'):
+ info['type'] = 'tour'
+ elif mg.get('RING'):
+ info['type'] = 'ring'
+
info['currency'] = 'EUR'
if 'LIMIT' in mg:
@@ -202,11 +220,62 @@ class Winamax(HandHistoryConverter):
hand.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "CET", "UTC")
if key == 'HID1':
- hand.handid = "1%.4d%s%s"%(int(info['HID2']),info['HID1'],info['HID3'])
# Need to remove non-alphanumerics for MySQL
+ hand.handid = "1%.9d%s%s"%(int(info['HID2']),info['HID1'],info['HID3'])
+ if key == 'TOURNO':
+ hand.tourNo = info[key]
if key == 'TABLE':
hand.tablename = info[key]
+ if key == 'BUYIN':
+ if hand.tourNo!=None:
+ #print "DEBUG: info['BUYIN']: %s" % info['BUYIN']
+ #print "DEBUG: info['BIAMT']: %s" % info['BIAMT']
+ #print "DEBUG: info['BIRAKE']: %s" % info['BIRAKE']
+ #print "DEBUG: info['BOUNTY']: %s" % info['BOUNTY']
+ for k in ['BIAMT','BIRAKE']:
+ if k in info.keys() and info[k]:
+ info[k] = info[k].replace(',','.')
+
+ if info[key] == 'Freeroll':
+ hand.buyin = 0
+ hand.fee = 0
+ hand.buyinCurrency = "FREE"
+ else:
+ if info[key].find("$")!=-1:
+ hand.buyinCurrency="USD"
+ elif info[key].find(u"€")!=-1:
+ hand.buyinCurrency="EUR"
+ elif info[key].find("FPP")!=-1:
+ hand.buyinCurrency="PSFP"
+ else:
+ #FIXME: handle other currencies, FPP, play money
+ raise FpdbParseError(_("failed to detect currency"))
+
+ info['BIAMT'] = info['BIAMT'].strip(u'$€FPP')
+
+ if hand.buyinCurrency!="PSFP":
+ if info['BOUNTY'] != None:
+ # There is a bounty, Which means we need to switch BOUNTY and BIRAKE values
+ tmp = info['BOUNTY']
+ info['BOUNTY'] = info['BIRAKE']
+ info['BIRAKE'] = tmp
+ info['BOUNTY'] = info['BOUNTY'].strip(u'$€') # Strip here where it isn't 'None'
+ hand.koBounty = int(100*Decimal(info['BOUNTY']))
+ hand.isKO = True
+ else:
+ hand.isKO = False
+
+ info['BIRAKE'] = info['BIRAKE'].strip(u'$€')
+ hand.buyin = int(100*Decimal(info['BIAMT']))
+ hand.fee = int(100*Decimal(info['BIRAKE']))
+ else:
+ hand.buyin = int(Decimal(info['BIAMT']))
+ hand.fee = 0
+
+ if key == 'LEVEL':
+ hand.level = info[key]
+
# TODO: These
hand.buttonpos = 1
hand.maxseats = 10 # Set to None - Hand.py will guessMaxSeats()
diff --git a/pyfpdb/XTables.py b/pyfpdb/XTables.py
index b9a363d0..b082b8b2 100644
--- a/pyfpdb/XTables.py
+++ b/pyfpdb/XTables.py
@@ -44,6 +44,10 @@ class Table(Table_Window):
def find_table_parameters(self):
+# This is called by __init__(). Find the poker table window of interest,
+# given the self.search_string. Then populate self.number, self.title,
+# self.window, and self.parent (if required).
+
reg = '''
\s+(?P[\dxabcdef]+) # XID in hex
\s(?P.+): # window title
@@ -53,12 +57,10 @@ class Table(Table_Window):
for listing in os.popen('xwininfo -root -tree').readlines():
if re.search(self.search_string, listing, re.I):
mo = re.match(reg, listing, re.VERBOSE)
-# mo = re.match('\s+([\dxabcdef]+) (.+):\s\(\"([a-zA-Z0-9\-.]+)\".+ (\d+)x(\d+)\+\d+\+\d+ \+(\d+)\+(\d+)', listing)
title = re.sub('\"', '', mo.groupdict()["TITLE"])
if self.check_bad_words(title): continue
self.number = int( mo.groupdict()["XID"], 0 )
self.title = title
- self.hud = None # specified later
break
if self.number is None:
diff --git a/pyfpdb/fpdb.pyw b/pyfpdb/fpdb.pyw
index 91a0ebef..3660dd57 100755
--- a/pyfpdb/fpdb.pyw
+++ b/pyfpdb/fpdb.pyw
@@ -116,6 +116,7 @@ import GuiAutoImport
import GuiGraphViewer
import GuiTourneyGraphViewer
import GuiSessionViewer
+import GuiStove
import SQL
import Database
import Configuration
@@ -778,6 +779,7 @@ class fpdb:
+