diff --git a/pyfpdb/Configuration.py b/pyfpdb/Configuration.py old mode 100755 new mode 100644 index 9093c22f..9c14d16f --- a/pyfpdb/Configuration.py +++ b/pyfpdb/Configuration.py @@ -59,28 +59,44 @@ def get_exec_path(): return sys.path[0] def get_config(file_name, fallback = True): - """Looks in exec dir and in self.default_config_path for a config file.""" - config_path = os.path.join(DIR_SELF, file_name) # look in exec dir - if os.path.exists(config_path) and os.path.isfile(config_path): - return config_path # there is a file in the exec dir so we use it - else: - config_path = os.path.join(DIR_CONFIG, file_name) # look in config dir - if os.path.exists(config_path) and os.path.isfile(config_path): + """Looks in cwd and in self.default_config_path for a config file.""" + exec_dir = get_exec_path() + 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 # 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 # No file found if not fallback: return False -# OK, fall back to the .example file, should be in the exec dir - if os.path.exists(os.path.join(DIR_SELF, file_name + ".example")): +# OK, fall back to the .example file, should be in the start dir + if os.path.exists(file_name + ".example"): try: - shutil.copyfile(os.path.join(DIR_SELF, file_name + ".example"), os.path.join(DIR_CONFIG, file_name)) - print "No %s found, using %s.example.\n" % (file_name, file_name) - print "A %s file has been created. You will probably have to edit it." % os.path.join(DIR_CONFIG, file_name) - log.error("No %s found, using %s.example.\n" % (file_name, file_name) ) + print "" + if not os.path.isdir(default_dir): + msg = "Creating directory: '%s'" % (default_dir) + print msg + logging.info(msg) + os.mkdir(default_dir) + shutil.copyfile(file_name + ".example", config_path) + msg = "No %s found in %s or %s\n" % (file_name, exec_dir, default_dir) \ + + "Config file has been created at %s.\n" % config_path \ + + "Edit your screen_name and hand history path in the supported_sites "\ + + "section of the \nPreferences window (Main menu) before trying to import hands." + print msg + logging.info(msg) + file_name = config_path except: - print "No %s found, cannot fall back. Exiting.\n" % file_name + 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: print "No %s found, cannot fall back. Exiting.\n" % file_name @@ -94,26 +110,18 @@ def get_logger(file_name, config = "config", fallback = False): try: logging.config.fileConfig(conf) log = logging.getLogger(config) + log.debug("%s logger initialised" % config) return log except: pass log = logging.basicConfig() log = logging.getLogger() - log.error("basicConfig logger initialised") + log.debug("config logger initialised") return log -def check_dir(path, create = True): - """Check if a dir exists, optionally creates if not.""" - if os.path.exists(path): - if os.path.isdir(path): - return path - else: - return False - if create: - print "creating directory %s" % path - else: - return False +# find a logging.conf file and set up logging +log = get_logger("logging.conf") ######################################################################## # application wide consts @@ -121,32 +129,20 @@ def check_dir(path, create = True): APPLICATION_NAME_SHORT = 'fpdb' APPLICATION_VERSION = 'xx.xx.xx' -DIR_SELF = get_exec_path() -DIR_CONFIG = check_dir(get_default_config_path()) -DIR_DATABASE = check_dir(os.path.join(DIR_CONFIG, 'database')) -DIR_LOG = check_dir(os.path.join(DIR_CONFIG, 'log')) - DATABASE_TYPE_POSTGRESQL = 'postgresql' DATABASE_TYPE_SQLITE = 'sqlite' DATABASE_TYPE_MYSQL = 'mysql' -#TODO: should this be a tuple or a dict DATABASE_TYPES = ( DATABASE_TYPE_POSTGRESQL, DATABASE_TYPE_SQLITE, DATABASE_TYPE_MYSQL, ) -# find a logging.conf file and set up logging -log = get_logger("logging.conf", config = "config") -log.debug("config logger initialised") - -# and then log our consts -log.info("DIR SELF = %s" % DIR_SELF) -log.info("DIR CONFIG = %s" % DIR_CONFIG) -log.info("DIR DATABASE = %s" % DIR_DATABASE) -log.info("DIR LOG = %s" % DIR_LOG) -NEWIMPORT = True -LOCALE_ENCODING = locale.getdefaultlocale()[1] +#LOCALE_ENCODING = locale.getdefaultlocale()[1] +LOCALE_ENCODING = locale.getpreferredencoding() +if LOCALE_ENCODING == "US-ASCII": + print "Default encoding set to US-ASCII, defaulting to CP1252 instead -- If you're not on a Mac, please report this problem." + LOCALE_ENCODING = "cp1252" ######################################################################## def string_to_bool(string, default=True): @@ -428,7 +424,8 @@ class Config: if file is not None: # config file path passed in file = os.path.expanduser(file) if not os.path.exists(file): - log.error("Specified configuration file %s not found. Using defaults." % (file)) + print "Configuration file %s not found. Using defaults." % (file) + sys.stderr.write("Configuration file %s not found. Using defaults." % (file)) file = None if file is None: file = get_config("HUD_config.xml", True) diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py index 5cd25d9d..54690979 100644 --- a/pyfpdb/Database.py +++ b/pyfpdb/Database.py @@ -39,41 +39,38 @@ import string import re import Queue import codecs -import logging import math # pyGTK modules +# FreePokerTools modules +import SQL +import Card +import Tourney +import Charset +from Exceptions import * +import Configuration +log = Configuration.get_logger("logging.conf","db") + + # Other library modules try: import sqlalchemy.pool as pool use_pool = True except ImportError: - logging.info("Not using sqlalchemy connection pool.") + log.info("Not using sqlalchemy connection pool.") use_pool = False try: from numpy import var use_numpy = True except ImportError: - logging.info("Not using numpy to define variance in sqlite.") + log.info("Not using numpy to define variance in sqlite.") use_numpy = False -# FreePokerTools modules -import Configuration -import SQL -import Card -import Tourney -import Charset -from Exceptions import * - -log = Configuration.get_logger("logging.conf", config = "db") -log.debug("db logger initialized.") -encoder = codecs.lookup('utf-8') - DB_VERSION = 119 @@ -247,13 +244,14 @@ class Database: # connect to db self.do_connect(c) - print "connection =", self.connection + if self.backend == self.PGSQL: from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT, ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE #ISOLATION_LEVEL_AUTOCOMMIT = 0 #ISOLATION_LEVEL_READ_COMMITTED = 1 #ISOLATION_LEVEL_SERIALIZABLE = 2 + if self.backend == self.SQLITE and self.database == ':memory:' and self.wrongDbVersion: log.info("sqlite/:memory: - creating") self.recreate_tables() @@ -374,20 +372,20 @@ class Database: print msg raise FpdbError(msg) elif backend == Database.SQLITE: - logging.info("Connecting to SQLite: %(database)s" % {'database':database}) + log.info("Connecting to SQLite: %(database)s" % {'database':database}) import sqlite3 if use_pool: sqlite3 = pool.manage(sqlite3, pool_size=1) else: - logging.warning("SQLite won't work well without 'sqlalchemy' installed.") + log.warning("SQLite won't work well without 'sqlalchemy' installed.") if database != ":memory:": if not os.path.isdir(self.config.dir_databases): print "Creating directory: '%s'" % (self.config.dir_databases) - logging.info("Creating directory: '%s'" % (self.config.dir_databases)) + log.info("Creating directory: '%s'" % (self.config.dir_databases)) os.mkdir(self.config.dir_databases) database = os.path.join(self.config.dir_databases, database) - logging.info(" sqlite db: " + database) + log.info(" sqlite db: " + database) self.connection = sqlite3.connect(database, detect_types=sqlite3.PARSE_DECLTYPES ) sqlite3.register_converter("bool", lambda x: bool(int(x))) sqlite3.register_adapter(bool, lambda x: "1" if x else "0") @@ -397,7 +395,10 @@ class Database: if use_numpy: self.connection.create_aggregate("variance", 1, VARIANCE) else: - logging.warning("Some database functions will not work without NumPy support") + log.warning("Some database functions will not work without NumPy support") + self.cursor = self.connection.cursor() + self.cursor.execute('PRAGMA temp_store=2') # use memory for temp tables/indexes + self.cursor.execute('PRAGMA synchronous=0') # don't wait for file writes to finish else: raise FpdbError("unrecognised database backend:"+backend) @@ -412,7 +413,7 @@ class Database: self.cursor.execute("SELECT * FROM Settings") settings = self.cursor.fetchone() if settings[0] != DB_VERSION: - logging.error("outdated or too new database version (%s) - please recreate tables" + log.error("outdated or too new database version (%s) - please recreate tables" % (settings[0])) self.wrongDbVersion = True except:# _mysql_exceptions.ProgrammingError: @@ -422,11 +423,6 @@ class Database: log.info("failed to read settings table - recreating tables") self.recreate_tables() self.check_version(database=database, create=False) - if not self.wrongDbVersion: - msg = "Edit your screen_name and hand history path in the supported_sites "\ - +"section of the \nPreferences window (Main menu) before trying to import hands" - print "\n%s" % msg - log.warning(msg) else: print "Failed to read settings table - please recreate tables" log.info("failed to read settings table - please recreate tables") @@ -436,7 +432,27 @@ class Database: #end def connect def commit(self): - self.connection.commit() + if self.backend != self.SQLITE: + self.connection.commit() + else: + # sqlite commits can fail because of shared locks on the database (SQLITE_BUSY) + # re-try commit if it fails in case this happened + maxtimes = 5 + pause = 1 + ok = False + for i in xrange(maxtimes): + try: + ret = self.connection.commit() + log.debug("commit finished ok, i = "+str(i)) + ok = True + except: + log.debug("commit "+str(i)+" failed: info=" + str(sys.exc_info()) + + " value=" + str(sys.exc_value)) + sleep(pause) + if ok: break + if not ok: + log.debug("commit failed") + raise FpdbError('sqlite commit failed') def rollback(self): self.connection.rollback() @@ -1311,7 +1327,6 @@ class Database: c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')") c.execute("INSERT INTO Sites (name,currency) VALUES ('Partouche', 'EUR')") - c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')") if self.backend == self.SQLITE: c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);") elif self.backend == self.PGSQL: @@ -1751,7 +1766,10 @@ class Database: def insertPlayer(self, name, site_id): result = None - _name = Charset.to_db_utf8(name) + if self.backend == self.SQLITE: + _name = name + else: + _name = Charset.to_db_utf8(name) c = self.get_cursor() q = "SELECT name, id FROM Players WHERE siteid=%s and name=%s" q = q.replace('%s', self.sql.query['placeholder']) @@ -1907,7 +1925,7 @@ class Database: # end def send_finish_msg(): def tRecogniseTourneyType(self, tourney): - logging.debug("Database.tRecogniseTourneyType") + log.debug("Database.tRecogniseTourneyType") typeId = 1 # Check if Tourney exists, and if so retrieve TTypeId : in that case, check values of the ttype cursor = self.get_cursor() @@ -1923,10 +1941,10 @@ class Database: try: len(result) typeId = result[0] - logging.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId) + log.debug("Tourney found in db with Tourney_Type_ID = %d" % typeId) for ev in expectedValues : if ( getattr( tourney, expectedValues.get(ev) ) <> result[ev] ): - logging.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) ) + log.debug("TypeId mismatch : wrong %s : Tourney=%s / db=%s" % (expectedValues.get(ev), getattr( tourney, expectedValues.get(ev)), result[ev]) ) typeIdMatch = False #break except: @@ -1936,7 +1954,7 @@ class Database: if typeIdMatch == False : # Check for an existing TTypeId that matches tourney info (buyin/fee, knockout, rebuy, speed, matrix, shootout) # if not found create it - logging.debug("Searching for a TourneyTypeId matching TourneyType data") + log.debug("Searching for a TourneyTypeId matching TourneyType data") cursor.execute (self.sql.query['getTourneyTypeId'].replace('%s', self.sql.query['placeholder']), (tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix) @@ -1946,9 +1964,9 @@ class Database: try: len(result) typeId = result[0] - logging.debug("Existing Tourney Type Id found : %d" % typeId) + log.debug("Existing Tourney Type Id found : %d" % typeId) except TypeError: #this means we need to create a new entry - logging.debug("Tourney Type Id not found : create one") + log.debug("Tourney Type Id not found : create one") cursor.execute (self.sql.query['insertTourneyTypes'].replace('%s', self.sql.query['placeholder']), (tourney.siteId, tourney.buyin, tourney.fee, tourney.isKO, tourney.isRebuy, tourney.speed, tourney.isHU, tourney.isShootout, tourney.isMatrix) diff --git a/pyfpdb/Exceptions.py b/pyfpdb/Exceptions.py index fd7c20e3..789c7b83 100644 --- a/pyfpdb/Exceptions.py +++ b/pyfpdb/Exceptions.py @@ -48,5 +48,11 @@ class FpdbPostgresqlNoDatabase(FpdbDatabaseError): def __str__(self): return repr(self.value +" " + self.errmsg) -class DuplicateError(FpdbError): +class FpdbHandError(FpdbError): + pass + +class FpdbHandDuplicate(FpdbHandError): + pass + +class FpdbHandPartial(FpdbHandError): pass diff --git a/pyfpdb/GuiAutoImport.py b/pyfpdb/GuiAutoImport.py index f9f1c335..628f7b7f 100755 --- a/pyfpdb/GuiAutoImport.py +++ b/pyfpdb/GuiAutoImport.py @@ -194,7 +194,7 @@ class GuiAutoImport (threading.Thread): widget.set_label(u' _Stop Autoimport ') if self.pipe_to_hud is None: if os.name == 'nt': - command = "python HUD_main.py " + self.settings['cl_options'] + command = "python "+sys.path[0]+"\\HUD_main.py " + self.settings['cl_options'] bs = 0 else: command = os.path.join(sys.path[0], 'HUD_main.py') diff --git a/pyfpdb/HUD_main.py b/pyfpdb/HUD_main.py index 2a1829ae..df144e92 100755 --- a/pyfpdb/HUD_main.py +++ b/pyfpdb/HUD_main.py @@ -212,6 +212,7 @@ class HUD_main(object): # get basic info about the new hand from the db # if there is a db error, complain, skip hand, and proceed + log.info("HUD_main.read_stdin: hand processing starting ...") try: (table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \ self.db_connection.get_table_info(new_hand_id) @@ -237,6 +238,7 @@ class HUD_main(object): 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']) + t4 = 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]) self.hud_dict[temp_key].stat_dict = stat_dict except KeyError: # HUD instance has been killed off, key is stale @@ -245,6 +247,7 @@ class HUD_main(object): # Unlocks table, copied from end of function self.db_connection.connection.rollback() return + 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! @@ -257,10 +260,8 @@ 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! diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py index 93661d70..9c7ea23f 100644 --- a/pyfpdb/Hand.py +++ b/pyfpdb/Hand.py @@ -229,8 +229,7 @@ db: a connected Database object""" # TourneysPlayers else: log.info("Hand.insert(): hid #: %s is a duplicate" % hh['siteHandNo']) - #Raise Duplicate exception? - pass + raise FpdbHandDuplicate(hh['siteHandNo']) def updateHudCache(self, db): db.storeHudCache(self.dbid_gt, self.dbid_pids, self.starttime, self.stats.getHandsPlayers()) @@ -636,7 +635,7 @@ class HoldemOmahaHand(Hand): if builtFrom == "HHC": hhc.readHandInfo(self) if self.gametype['type'] == 'tour': - self.tablename = "%s %s" % (self.tourNo, self.tablename) + self.tablename = "%s %s" % (self.tourNo, self.tablename) hhc.readPlayerStacks(self) hhc.compilePlayerRegexs(self) hhc.markStreets(self) @@ -932,7 +931,7 @@ class DrawHand(Hand): if builtFrom == "HHC": hhc.readHandInfo(self) if self.gametype['type'] == 'tour': - self.tablename = "%s %s" % (self.tourNo, self.tablename) + self.tablename = "%s %s" % (self.tourNo, self.tablename) hhc.readPlayerStacks(self) hhc.compilePlayerRegexs(self) hhc.markStreets(self) @@ -1128,7 +1127,7 @@ class StudHand(Hand): if builtFrom == "HHC": hhc.readHandInfo(self) if self.gametype['type'] == 'tour': - self.tablename = "%s %s" % (self.tourNo, self.tablename) + self.tablename = "%s %s" % (self.tourNo, self.tablename) hhc.readPlayerStacks(self) hhc.compilePlayerRegexs(self) hhc.markStreets(self) @@ -1220,7 +1219,7 @@ Add a complete on [street] by [player] to [amountTo] # showdownPot INT, /* pot size at sd/street7 */ return (0,0,0,0,0) - + def writeHand(self, fh=sys.__stdout__): # PokerStars format. @@ -1615,4 +1614,3 @@ ORDER BY return h - diff --git a/pyfpdb/fpdb.py b/pyfpdb/fpdb.py index 5136d56b..f202698d 100755 --- a/pyfpdb/fpdb.py +++ b/pyfpdb/fpdb.py @@ -769,7 +769,7 @@ class fpdb: self.status_bar.show() 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.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() @@ -902,8 +902,8 @@ This program is licensed under the AGPL3, see docs"""+os.sep+"agpl-3.0.txt") self.load_profile() self.statusIcon = gtk.StatusIcon() - if os.path.exists('../gfx/fpdb-cards.png'): - self.statusIcon.set_from_file('../gfx/fpdb-cards.png') + if os.path.exists(os.path.join(sys.path[0], '../gfx/fpdb-cards.png')): + self.statusIcon.set_from_file(os.path.join(sys.path[0], '../gfx/fpdb-cards.png')) elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'): self.statusIcon.set_from_file('/usr/share/pixmaps/fpdb-cards.png') else: diff --git a/pyfpdb/fpdb_import.py b/pyfpdb/fpdb_import.py index fe66fcda..859a6bc0 100644 --- a/pyfpdb/fpdb_import.py +++ b/pyfpdb/fpdb_import.py @@ -365,7 +365,7 @@ class Importer: pass (stored, duplicates, partial, errors, ttime) = self.import_file_dict(self.database, file, self.filelist[file][0], self.filelist[file][1], None) try: - if not os.path.isdir(file): + if not os.path.isdir(file): # Note: This assumes that whatever calls us has an "addText" func self.caller.addText(" %d stored, %d duplicates, %d partial, %d errors (time = %f)" % (stored, duplicates, partial, errors, ttime)) except KeyError: # TODO: Again, what error happens here? fix when we find out .. pass @@ -402,7 +402,7 @@ class Importer: return (0,0,0,0,0) conv = None - (stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, 0) + (stored, duplicates, partial, errors, ttime) = (0, 0, 0, 0, time()) file = file.decode(Configuration.LOCALE_ENCODING) @@ -437,12 +437,15 @@ class Importer: for hand in handlist: if hand is not None: - #try, except duplicates here? hand.prepInsert(self.database) - hand.insert(self.database) - if self.callHud and hand.dbid_hands != 0: - to_hud.append(hand.dbid_hands) - else: + try: + hand.insert(self.database) + except Exceptions.FpdbHandDuplicate: + duplicates += 1 + else: + if self.callHud and hand.dbid_hands != 0: + to_hud.append(hand.dbid_hands) + else: # TODO: Treat empty as an error, or just ignore? log.error("Hand processed but empty") self.database.commit() # Call hudcache update if not in bulk import mode @@ -460,13 +463,16 @@ class Importer: errors = getattr(hhc, 'numErrors') stored = getattr(hhc, 'numHands') + stored -= duplicates else: # conversion didn't work # TODO: appropriate response? - return (0, 0, 0, 1, 0) + return (0, 0, 0, 1, time() - ttime) else: log.warning("Unknown filter filter_name:'%s' in filter:'%s'" %(filter_name, filter)) - return (0, 0, 0, 1, 0) + return (0, 0, 0, 1, time() - ttime) + + ttime = time() - ttime #This will barf if conv.getStatus != True return (stored, duplicates, partial, errors, ttime) diff --git a/run_fpdb.py b/run_fpdb.py index 24f6a4dd..78e2803b 100755 --- a/run_fpdb.py +++ b/run_fpdb.py @@ -1,34 +1,34 @@ -#!/usr/bin/python - -#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 os -import sys - -# sys.path[0] holds the directory run_fpdb.py is in -sys.path[0] = sys.path[0]+os.sep+"pyfpdb" -os.chdir(sys.path[0]) -#print "sys.path[0] =", sys.path[0], "cwd =", os.getcwd() - - -import fpdb - - -if __name__ == "__main__": - me = fpdb.fpdb() - me.main() - exit() +#!/usr/bin/python + +#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 os +import sys + +# sys.path[0] holds the directory run_fpdb.py is in +sys.path[0] = sys.path[0]+os.sep+"pyfpdb" +os.chdir(sys.path[0]) +#print "sys.path[0] =", sys.path[0], "cwd =", os.getcwd() + + +import fpdb + + +if __name__ == "__main__": + me = fpdb.fpdb() + me.main() + exit()