2008-08-04 05:44:28 +02:00
#!/usr/bin/python
#Copyright 2008 Steffen Jobbagy-Felso
#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 <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
#see status.txt for site/games support info
import sys
2008-09-16 23:19:50 +02:00
try :
import MySQLdb
mysqlLibFound = True
except :
pass
try :
import psycopg2
pgsqlLibFound = True
except :
pass
2008-08-04 05:44:28 +02:00
import math
import os
import datetime
2008-10-16 15:21:11 +02:00
import re
2008-08-04 05:44:28 +02:00
import fpdb_simple
import fpdb_parse_logic
2008-10-09 20:18:54 +02:00
from time import time
2008-08-04 05:44:28 +02:00
2008-10-08 19:36:08 +02:00
class Importer :
2008-08-04 05:44:28 +02:00
2008-10-11 19:19:57 +02:00
def __init__ ( self , caller , settings ) :
2008-10-08 19:36:08 +02:00
""" Constructor """
2008-10-11 19:12:30 +02:00
self . settings = settings
2008-10-11 19:19:57 +02:00
self . caller = caller
2008-10-09 18:13:56 +02:00
self . db = None
self . cursor = None
2008-11-05 12:36:24 +01:00
self . filelist = { }
self . dirlist = { }
2008-10-12 19:26:04 +02:00
self . monitor = False
2008-10-16 19:36:02 +02:00
self . updated = { } #Time last import was run {file:mtime}
2008-10-09 19:21:01 +02:00
self . callHud = False
2008-10-09 20:18:54 +02:00
self . lines = None
2008-10-16 15:21:11 +02:00
self . faobs = None #File as one big string
2008-10-10 19:42:09 +02:00
self . pos_in_file = { } # dict to remember how far we have read in the file
2008-10-11 19:42:08 +02:00
#Set defaults
2008-10-11 19:12:30 +02:00
if not self . settings . has_key ( ' imp-callFpdbHud ' ) :
self . settings [ ' imp-callFpdbHud ' ] = False
2008-10-11 19:42:08 +02:00
if not self . settings . has_key ( ' minPrint ' ) :
self . settings [ ' minPrint ' ] = 30
2008-10-11 19:12:30 +02:00
self . dbConnect ( )
2008-10-08 19:36:08 +02:00
2008-10-11 19:12:30 +02:00
def dbConnect ( self ) :
2008-08-04 05:44:28 +02:00
#connect to DB
2008-10-11 19:12:30 +02:00
if self . settings [ ' db-backend ' ] == 2 :
2008-09-16 23:19:50 +02:00
if not mysqlLibFound :
raise fpdb_simple . FpdbError ( " interface library MySQLdb not found but MySQL selected as backend - please install the library or change the config file " )
2008-10-11 19:12:30 +02:00
self . db = MySQLdb . connect ( self . settings [ ' db-host ' ] , self . settings [ ' db-user ' ] ,
self . settings [ ' db-password ' ] , self . settings [ ' db-databaseName ' ] )
elif self . settings [ ' db-backend ' ] == 3 :
2008-09-16 23:19:50 +02:00
if not pgsqlLibFound :
raise fpdb_simple . FpdbError ( " interface library psycopg2 not found but PostgreSQL selected as backend - please install the library or change the config file " )
2008-11-07 17:07:42 +01:00
print self . settings
self . db = psycopg2 . connect ( host = self . settings [ ' db-host ' ] ,
user = self . settings [ ' db-user ' ] ,
password = self . settings [ ' db-password ' ] ,
database = self . settings [ ' db-databaseName ' ] )
2008-10-11 19:12:30 +02:00
elif self . settings [ ' db-backend ' ] == 4 :
2008-09-15 22:31:55 +02:00
pass
else :
pass
2008-10-09 18:13:56 +02:00
self . cursor = self . db . cursor ( )
2008-10-12 09:49:09 +02:00
#Set functions
2008-10-09 19:21:01 +02:00
def setCallHud ( self , value ) :
self . callHud = value
2008-10-11 19:42:08 +02:00
def setMinPrint ( self , value ) :
self . settings [ ' minPrint ' ] = int ( value )
2008-10-11 20:14:06 +02:00
def setHandCount ( self , value ) :
self . settings [ ' handCount ' ] = int ( value )
def setQuiet ( self , value ) :
self . settings [ ' quiet ' ] = value
def setFailOnError ( self , value ) :
self . settings [ ' failOnError ' ] = value
2008-10-16 19:36:02 +02:00
# def setWatchTime(self):
# self.updated = time()
2008-10-12 09:49:09 +02:00
def clearFileList ( self ) :
2008-11-05 12:36:24 +01:00
self . filelist = { }
2008-10-12 09:49:09 +02:00
#Add an individual file to filelist
2008-11-05 12:36:24 +01:00
def addImportFile ( self , filename , site = " default " , filter = " passthrough " ) :
2008-10-17 20:09:34 +02:00
#TODO: test it is a valid file
2008-11-05 12:36:24 +01:00
self . filelist [ filename ] = [ site ] + [ filter ]
2008-10-12 09:49:09 +02:00
#Add a directory of files to filelist
2008-11-05 12:36:24 +01:00
#Only one import directory per site supported.
#dirlist is a hash of lists:
#dirlist{ 'PokerStars' => ["/path/to/import/", "filtername"] }
def addImportDirectory ( self , dir , monitor = False , site = " default " , filter = " passthrough " ) :
2008-10-17 20:09:34 +02:00
if os . path . isdir ( dir ) :
if monitor == True :
self . monitor = True
2008-11-05 12:36:24 +01:00
self . dirlist [ site ] = [ dir ] + [ filter ]
2008-10-17 20:09:34 +02:00
for file in os . listdir ( dir ) :
2008-11-05 12:36:24 +01:00
self . addImportFile ( os . path . join ( dir , file ) , site , filter )
2008-10-17 20:09:34 +02:00
else :
print " Warning: Attempted to add: ' " + str ( dir ) + " ' as an import directory "
2008-10-12 09:49:09 +02:00
#Run full import on filelist
def runImport ( self ) :
for file in self . filelist :
self . import_file_dict ( file )
#Run import on updated files, then store latest update time.
def runUpdated ( self ) :
2008-10-12 19:26:04 +02:00
#Check for new files in directory
#todo: make efficient - always checks for new file, should be able to use mtime of directory
# ^^ May not work on windows
2008-11-05 12:36:24 +01:00
for site in self . dirlist :
self . addImportDirectory ( self . dirlist [ site ] [ 0 ] , False , site , self . dirlist [ site ] [ 1 ] )
2008-10-12 19:26:04 +02:00
2008-10-12 09:49:09 +02:00
for file in self . filelist :
stat_info = os . stat ( file )
2008-10-16 19:36:02 +02:00
try :
lastupdate = self . updated [ file ]
if stat_info . st_mtime > lastupdate :
self . import_file_dict ( file )
self . updated [ file ] = time ( )
except :
self . updated [ file ] = time ( )
2008-11-04 22:39:27 +01:00
# This codepath only runs first time the file is found, if modified in the last
# minute run an immediate import.
if ( time ( ) - stat_info . st_mtime ) < 60 :
self . import_file_dict ( file )
2008-10-12 09:49:09 +02:00
# This is now an internal function that should not be called directly.
def import_file_dict ( self , file ) :
2008-10-09 20:18:54 +02:00
starttime = time ( )
2008-10-09 18:13:56 +02:00
last_read_hand = 0
2008-10-10 19:42:09 +02:00
loc = 0
2008-10-12 09:49:09 +02:00
if ( file == " stdin " ) :
2008-10-09 18:13:56 +02:00
inputFile = sys . stdin
else :
2008-10-12 09:49:09 +02:00
inputFile = open ( file , " rU " )
try : loc = self . pos_in_file [ file ]
2008-10-10 19:42:09 +02:00
except : pass
2008-10-09 18:13:56 +02:00
2008-10-09 20:18:54 +02:00
# Read input file into class and close file
2008-10-10 19:42:09 +02:00
inputFile . seek ( loc )
2008-10-09 20:18:54 +02:00
self . lines = fpdb_simple . removeTrailingEOL ( inputFile . readlines ( ) )
2008-10-12 09:49:09 +02:00
self . pos_in_file [ file ] = inputFile . tell ( )
2008-10-09 20:18:54 +02:00
inputFile . close ( )
firstline = self . lines [ 0 ]
if firstline . find ( " Tournament Summary " ) != - 1 :
2008-09-22 23:48:12 +02:00
print " TODO: implement importing tournament summaries "
2008-11-10 03:02:12 +01:00
#self.faobs = readfile(inputFile)
#self.parseTourneyHistory()
2008-09-22 23:48:12 +02:00
return 0
2008-10-09 20:18:54 +02:00
site = fpdb_simple . recogniseSite ( firstline )
category = fpdb_simple . recogniseCategory ( firstline )
2008-08-04 05:44:28 +02:00
startpos = 0
stored = 0 #counter
duplicates = 0 #counter
partial = 0 #counter
errors = 0 #counter
2008-10-09 20:18:54 +02:00
for i in range ( len ( self . lines ) ) : #main loop, iterates through the lines of a file and calls the appropriate parser method
if ( len ( self . lines [ i ] ) < 2 ) :
2008-08-04 05:44:28 +02:00
endpos = i
2008-10-09 20:18:54 +02:00
hand = self . lines [ startpos : endpos ]
2008-08-04 05:44:28 +02:00
if ( len ( hand [ 0 ] ) < 2 ) :
hand = hand [ 1 : ]
cancelled = False
damaged = False
if ( site == " ftp " ) :
for i in range ( len ( hand ) ) :
if ( hand [ i ] . endswith ( " has been canceled " ) ) : #this is their typo. this is a typo, right?
cancelled = True
seat1 = hand [ i ] . find ( " Seat " ) #todo: make this recover by skipping this line
if ( seat1 != - 1 ) :
if ( hand [ i ] . find ( " Seat " , seat1 + 3 ) != - 1 ) :
damaged = True
if ( len ( hand ) < 3 ) :
pass
2008-10-09 20:53:57 +02:00
#todo: the above 2 lines are kind of a dirty hack, the mentioned circumstances should be handled elsewhere but that doesnt work with DOS/Win EOL. actually this doesnt work.
2008-08-04 05:44:28 +02:00
elif ( hand [ 0 ] . endswith ( " (partial) " ) ) : #partial hand - do nothing
partial + = 1
elif ( hand [ 1 ] . find ( " Seat " ) == - 1 and hand [ 2 ] . find ( " Seat " ) == - 1 and hand [ 3 ] . find ( " Seat " ) == - 1 ) : #todo: should this be or instead of and?
partial + = 1
elif ( cancelled or damaged ) :
partial + = 1
else : #normal processing
2008-08-18 08:43:05 +02:00
isTourney = fpdb_simple . isTourney ( hand [ 0 ] )
if not isTourney :
fpdb_simple . filterAnteBlindFold ( site , hand )
hand = fpdb_simple . filterCrap ( site , hand , isTourney )
2008-10-10 05:14:26 +02:00
self . hand = hand
2008-08-04 05:44:28 +02:00
try :
2008-10-09 18:13:56 +02:00
handsId = fpdb_parse_logic . mainParser ( self . db , self . cursor , site , category , hand )
self . db . commit ( )
2008-08-18 16:41:34 +02:00
2008-08-04 05:44:28 +02:00
stored + = 1
2008-10-09 18:13:56 +02:00
self . db . commit ( )
2008-10-09 19:21:01 +02:00
# if settings['imp-callFpdbHud'] and self.callHud and os.sep=='/':
2008-10-11 19:12:30 +02:00
if self . settings [ ' imp-callFpdbHud ' ] and self . callHud :
2008-08-22 22:10:32 +02:00
#print "call to HUD here. handsId:",handsId
2008-08-31 04:06:24 +02:00
#pipe the Hands.id out to the HUD
2008-10-11 19:19:57 +02:00
self . caller . pipe_to_hud . stdin . write ( " %s " % ( handsId ) + os . linesep )
2008-08-04 05:44:28 +02:00
except fpdb_simple . DuplicateError :
duplicates + = 1
except ( ValueError ) , fe :
errors + = 1
2008-10-12 09:49:09 +02:00
self . printEmailErrorMessage ( errors , file , hand [ 0 ] )
2008-10-09 20:36:12 +02:00
2008-10-11 20:14:06 +02:00
if ( self . settings [ ' failOnError ' ] ) :
2008-10-11 19:42:08 +02:00
self . db . commit ( ) #dont remove this, in case hand processing was cancelled.
2008-08-04 05:44:28 +02:00
raise
except ( fpdb_simple . FpdbError ) , fe :
errors + = 1
2008-10-12 09:49:09 +02:00
self . printEmailErrorMessage ( errors , file , hand [ 0 ] )
2008-10-09 20:36:12 +02:00
2008-08-04 05:44:28 +02:00
#fe.printStackTrace() #todo: get stacktrace
2008-10-09 18:13:56 +02:00
self . db . rollback ( )
2008-08-04 05:44:28 +02:00
2008-10-11 20:14:06 +02:00
if ( self . settings [ ' failOnError ' ] ) :
2008-10-11 19:42:08 +02:00
self . db . commit ( ) #dont remove this, in case hand processing was cancelled.
2008-08-04 05:44:28 +02:00
raise
2008-10-11 19:42:08 +02:00
if ( self . settings [ ' minPrint ' ] != 0 ) :
if ( ( stored + duplicates + partial + errors ) % self . settings [ ' minPrint ' ] == 0 ) :
2008-08-04 05:44:28 +02:00
print " stored: " , stored , " duplicates: " , duplicates , " partial: " , partial , " errors: " , errors
2008-10-11 20:14:06 +02:00
if ( self . settings [ ' handCount ' ] != 0 ) :
if ( ( stored + duplicates + partial + errors ) > = self . settings [ ' handCount ' ] ) :
if ( not self . settings [ ' quiet ' ] ) :
2008-08-04 05:44:28 +02:00
print " quitting due to reaching the amount of hands to be imported "
2008-10-09 20:18:54 +02:00
print " Total stored: " , stored , " duplicates: " , duplicates , " partial/damaged: " , partial , " errors: " , errors , " time: " , ( time ( ) - starttime )
2008-08-04 05:44:28 +02:00
sys . exit ( 0 )
startpos = endpos
2008-10-09 20:18:54 +02:00
print " Total stored: " , stored , " duplicates: " , duplicates , " partial: " , partial , " errors: " , errors , " time: " , ( time ( ) - starttime )
2008-08-04 05:44:28 +02:00
2008-10-09 08:17:18 +02:00
if stored == 0 :
if duplicates > 0 :
2008-10-09 20:18:54 +02:00
for line_no in range ( len ( self . lines ) ) :
if self . lines [ line_no ] . find ( " Game # " ) != - 1 :
final_game_line = self . lines [ line_no ]
2008-10-09 08:17:18 +02:00
handsId = fpdb_simple . parseSiteHandNo ( final_game_line )
else :
print " failed to read a single hand from file: " , inputFile
handsId = 0
#todo: this will cause return of an unstored hand number if the last hand was error or partial
2008-10-09 18:13:56 +02:00
self . db . commit ( )
2008-10-15 19:20:33 +02:00
self . handsId = handsId
2008-08-19 00:53:25 +02:00
return handsId
2008-10-09 08:17:18 +02:00
#end def import_file_dict
2008-08-04 05:44:28 +02:00
2008-10-16 15:21:11 +02:00
def parseTourneyHistory ( self ) :
print " Tourney history parser stub "
#Find tournament boundaries.
#print self.foabs
2008-10-09 20:36:12 +02:00
def printEmailErrorMessage ( self , errors , filename , line ) :
print " Error No. " , errors , " , please send the hand causing this to steffen@sycamoretest.info so I can fix it. "
2008-10-12 09:49:09 +02:00
print " Filename: " , filename
2008-10-09 20:36:12 +02:00
print " Here is the first line so you can identify it. Please mention that the error was a ValueError: "
2008-10-10 05:14:26 +02:00
print self . hand [ 0 ]
2008-10-09 20:36:12 +02:00
2008-08-04 05:44:28 +02:00
if __name__ == " __main__ " :
2008-11-07 01:14:25 +01:00
print " CLI for fpdb_import is now available as CliFpdb.py "