@python -x %~f0 %* & goto :EOF

import os
import re
import string
import sys
import time
import win32api
import win32con
import win32pipe
import msvcrt

from com.creo.araxi.database import GetAccountInfo

from pywintypes import TRUE, FALSE

EXIT_SUCCESS = 0
EXIT_FAILURE = 1
RESERVED = 0


HELP_MESSAGE = \
'''\
Look for duplicate fonts within a folder.

The duplicate fonts can be moved to a subfolder with the -M option.
If a font has one duplicate, the duplicate will go into FontDuplicates1.
If a font has two or more duplicates, the first duplicate goes into
FontDuplicates1, the second goes into FontDuplicates2, 
the third into FontDuplicates3, etc.

NOTE: After the duplicate fonts have been moved, change the font 
      search paths in any affected job(s).
      
Usage: FindDuplicateFonts [folder1] [ folder2] [folderN] [-m] [-h]

    folder(s)           \tFolder containing the fonts.
                        \tIf no folder is specified, will search all
                        \tfont search paths.
    -m                  \tMove duplicate fonts to FontDuplicates folder.
    -? -h -help         \tHelp screen.
'''
DEFAULT_SEARCH_PATHS = [ 'AdobeExtreme\\Bin\\Fonts'\
                       , 'AdobeExtreme\\Bin\\pgfonts'\
                       , 'AdobeExtreme\\Bin\\Resource\\CIDFonts'\
                       ]          

def main():
    araxiHome = os.environ['ARAXIHOME']
    fontSearchPathsFromUser, moveDuplicates = GetArgs()
    fontSearchPaths = []
    for path in DEFAULT_SEARCH_PATHS:
        fontSearchPaths.append( os.path.join( araxiHome, path ) )

    if fontSearchPathsFromUser == []:
        yn = raw_input( 'Do you wish to check all font search paths? (y/n) ' )
        if string.upper( yn ) == 'Y':
            print 'Please wait, getting font search paths from the database...'
            fontSearchPaths.extend( GetFontSearchPaths() )
        else:
            PrintUsageAndExit()
    else:
        fontSearchPaths.extend( fontSearchPathsFromUser )


    Log( '**********************' )
    Log( '*                    *' )
    Log( '* FindDuplicateFonts *' )
    Log( '*                    *' )
    Log( '**********************' )

    fontDictionary = {}
    for fontFolder in fontSearchPaths:
        if os.path.exists( fontFolder ):
            Log( 'Checking font search path: %s' % fontFolder )
            fontDictionary = GetPropertiesOfAllFonts( fontFolder, fontDictionary )
        else:
           Log( 'Path %s does not exist.' % fontFolder )

    FindDuplicateFonts( fontDictionary )
    if moveDuplicates == TRUE:
        MoveDuplicateFonts( fontDictionary )
        MoveNonPrinergyFonts( fontDictionary )


    Log( 'Done.' )


def GetPropertiesOfAllFonts( fontFolder, fontDictionary = {} ):
    files = os.listdir( fontFolder )
    fileCtr = 0
    fontCtr = 0
    for file in files:
        fontFoundFlag = FALSE
        fontName = None
        fullName = None
        filePath = os.path.join( fontFolder, file )
        if os.path.isfile( filePath ):
            fileCtr = fileCtr + 1

            hFile = open( filePath, 'r' )

            line = ' '
            fontProperties = []
            while line:
                line = hFile.readline()

                fn2 = re.search( '/FontName\W*/(.*?)\Wdef', line )
                if fn2:
                    fontName = fn2.group(1)
                    break

                fn = re.search( '/FullName.*?\((.*?)\).*def', line )
                if fn:
                     fullName = fn.group(1)

#                w = re.search( '/Weight.*?\((.*?)\).*def', line )
#                if w:
#                    fontProperties.append( w.group(1) )


#                uid = re.search( '/UniqueID\W*(\d*?)\Wdef', line )
#                if uid:
#                    fontProperties.append( uid.group(1) )
#                if len( fontProperties ) == 4:
#                if len( fontProperties ) == 2:

            hFile.close()
            if fontName <> None:
                fontProperties = ( fontName, )
            elif fullName <> None:
                fontProperties = ( fullName, )
            else:
#                print 'No font name! %s' % filePath
                continue
#            print fontProperties
#            fontProperties = tuple( fontProperties )

#            fontProperties = ( fontName, fullName )

            # add to existing entries, otherwise start with an empty file list
            if fontDictionary.has_key( fontProperties ):
                fontFileList = fontDictionary[ fontProperties ]
            else:
                fontFileList = []
            fontFileList.append( filePath )
            fontDictionary[ fontProperties ] = fontFileList
            fontCtr = fontCtr + 1

    Log( 'Processed %s files and found %s fonts.' % (fileCtr, fontCtr) )
    return fontDictionary


def FindDuplicateFonts( fontDictionary ):
    # Report all duplicate fonts
    keys = fontDictionary.keys()
    keys.sort()
    Log()
    Log( 'DUPLICATE FONTS' )
    Log( '---------------' )
    for fontProperties in keys:
        if fontProperties <> ():
            fileList = fontDictionary[ fontProperties ]

            if len( fileList ) > 1:
                msg = 'FONT: '

                for fontProperty in fontProperties:
                    msg = msg + ' %s, \t' % fontProperty
                Log( msg )
                Log( 'Found in:' )
                for file in fileList:
                    Log( '    %s' % file )


def MoveDuplicateFonts( fontDictionary ):
    # move all duplicate fonts
    keys = fontDictionary.keys()
    keys.sort()
    Log()
    Log( 'MOVING DUPLICATE FONTS' )
    Log( '----------------------' )
    for fontProperties in keys:
        if fontProperties <> ():
            fileList = fontDictionary[ fontProperties ]
            if len( fileList ) > 1:
                for i in range( 1, len( fileList ) ):
                    fileToMove = fileList[ i ]
                    fontFolder, fileName = os.path.split( fileToMove )
                    rootFolder, fontFolderName = os.path.split( fontFolder )
                    destinationFolder = os.path.join( rootFolder, '%s_FontDuplicates%d' % ( fontFolderName, i ) )

                    if not os.path.exists( destinationFolder ):
                        os.mkdir( destinationFolder )
   
                    Log( '    Moving %s to %s.' % ( fileToMove, destinationFolder ) )
                    win32api.MoveFile( fileToMove, os.path.join( destinationFolder, fileName ) )                              


def MoveNonPrinergyFonts( fontDictionary ):
    # Move all non-Prinergy fonts
    Log()
    Log( 'MOVING NON-PRINERGY FONTS' )
    Log( '-------------------------' )

    if fontDictionary.has_key( () ):
        fileList = fontDictionary[ () ]
        if len( fileList ) > 0:
            for fileToMove in fileList:
                fontFolder, fileName = os.path.split( fileToMove )
                rootFolder, fontFolderName = os.path.split( fontFolder )
#                print 'Could not find font name in file: %s ' % fileToMove
                destinationFolder = os.path.join( rootFolder, '%s_NonPrinergy' % fontFolderName )

                if not os.path.exists( destinationFolder ):
                    os.mkdir( destinationFolder )
                Log( '    Moving %s to %s.' % ( fileToMove, destinationFolder ) )
                win32api.MoveFile( fileToMove, os.path.join( destinationFolder, fileName ) )   


def GetFontSearchPaths():

    node = PrinergyNodeInfo( None )
    node.Query()
    primaryServer = string.upper( node.GetPrimaryNodeName() )
    prinergyVersion = node.GetCurrentRelease()

    sql = Db()
    
    query =\
    '''\
        SELECT DISTINCT '\\\\' || 
               j.jckdRootFolderServerName 
               || '\\' ||
               j.jckdRootFolderShareName
               || '\\' ||
               j.jckdRootFolderPathName
               || '\\' ||
               l.jacketLocListItemPathName
        FROM   DBCS_JacketLocationListItem l,
               DBCS_Jacket j
        WHERE  jacketLocListKindCd = 1
        AND    j.jckdId = l.jckdId
        AND    (
                   (        l.jacketLocListItemServerName = ' '
                     AND    l.jacketLocListItemShareName = ' '
                   )
        OR         (        l.jacketLocListItemServerName is NULL
                     AND    l.jacketLocListItemShareName is NULL
                   )
               )
        UNION
        SELECT DISTINCT '\\\\' || 
               l.jacketLocListItemServerName 
               || '\\' ||
               l.jacketLocListItemShareName
               || '\\' ||
               l.jacketLocListItemPathName
        FROM   DBCS_JacketLocationListItem l,
               DBCS_Jacket j
        WHERE  jacketLocListKindCd = 1
        AND    l.jacketLocListItemServerName <> ' '
        AND    l.jacketLocListItemShareName <> ' '



    '''
    results = sql.Query( query )

    fontSearchPaths = []
    for ( fontSearchPath, ) in results:
        fontSearchPath = re.sub( '%PRIMARY%', primaryServer, fontSearchPath )
        fontSearchPaths.append( fontSearchPath )
    sql.Close()

    return fontSearchPaths

#
# Get basic information about a Prinergy node.
# Divided into 4 steps:
# 1) Constructor just stores arguments
# 2) Query retrieves information, possibly raising exception
# 3) Format produces text, but does not write to a particular I/O device
# 4) Print writes formatted text to stdout
#
class PrinergyNodeInfo:
    def __init__( self, nodename ):
        self.nodename = NodeName( nodename )
        self.queried = FALSE
        
    def Query( self ):
        hklm = win32api.RegConnectRegistry( self.nodename, win32con.HKEY_LOCAL_MACHINE )
    
        subkey_Araxi = 'Software\\Creo\\Araxi'
        self.CurrentRelease = MyRegQueryValue( hklm, subkey_Araxi, 'CurrentRelease', win32con.REG_SZ )
    
        subkey_Araxi_CurrentRelease = subkey_Araxi + '\\' + self.CurrentRelease
        subkey_Araxi_CurrentRelease_Default = subkey_Araxi_CurrentRelease + '\\' + 'Default'
        subkey_Araxi_CurrentRelease_Default_System = subkey_Araxi_CurrentRelease_Default + '\\' + 'System'
        
        self.PrimaryNodeName = MyRegQueryValue( hklm, subkey_Araxi_CurrentRelease_Default_System, 'PrimaryNodeName', win32con.REG_SZ )
        self.NodeList = MyRegQueryValue( hklm, subkey_Araxi_CurrentRelease_Default_System, 'NodeList', win32con.REG_SZ )
        self.AraxiRootDirectory = MyRegQueryValue( hklm, subkey_Araxi_CurrentRelease_Default_System, 'AraxiRootDirectory', win32con.REG_SZ )
        try:
            # if the machine is a Emergency Plate Making (EPM) machine the OriginalPrimaryNodeName value will exist
            self.OriginalPrimaryNodeName = MyRegQueryValue( hklm, subkey_Araxi_CurrentRelease_Default_System, 'OriginalPrimaryNodeName', win32con.REG_SZ )
        except:
            self.OriginalPrimaryNodeName = None
        
        subkey = 'System\\CurrentControlSet\\Control\\Session Manager\\Environment'
        self.AraxiHomeEnvVar = MyRegQueryValue( hklm, subkey, 'AraxiHome', win32con.REG_SZ )
        
        subkey = 'System\\CurrentControlSet\\Services\\LanmanServer\\Shares'
        AraxiHomeShareInfo = MyRegQueryValue( hklm, subkey, 'AraxiHome', win32con.REG_MULTI_SZ )
        m = re.match( 'Path=(.+)', AraxiHomeShareInfo[1] )
        if m:
            self.AraxiHomeSharePath = m.group( 1 )
        else:
            self.AraxiHomeSharePath = '<Unavailable>'
        
        self.queried = 1
       
    
    def GetPrimaryNodeName( self ):
        if not self.queried:
            return '<Not queried>'

        return self.PrimaryNodeName
        
    def GetNodeName( self ):
        return self.nodename

    def GetCurrentRelease( self ):
        return self.CurrentRelease

def GetJobRootFolderPathPrefix( nodename = None ):
    hklm = win32api.RegConnectRegistry( nodename, win32con.HKEY_LOCAL_MACHINE )
  
    subkey_Araxi = 'Software\\Creo\\Araxi'
    currentRelease = MyRegQueryValue( hklm, subkey_Araxi, 'CurrentRelease', win32con.REG_SZ )
    
    subkey_Araxi_CurrentRelease = subkey_Araxi + '\\' + currentRelease
    subkey_Araxi_CurrentRelease_Default = subkey_Araxi_CurrentRelease + '\\' + 'Default'
    subkey_Araxi_CurrentRelease_Default_JobD = subkey_Araxi_CurrentRelease_Default + '\\' + 'JOBD'
    rootFolderPathPrefix = MyRegQueryValue( hklm, subkey_Araxi_CurrentRelease_Default_JobD, 'JobRootFolderPathPrefix', win32con.REG_SZ )

    return re.sub( '\\$', '', rootFolderPathPrefix )


#
# produce an NT computer name in 'canonical' form, without leading backslashes
# optional argument may have leading or missing double backslashes
# '.', '' or None is interpreted as the local computer
#
def NodeName( name=None ):
    nodename = ''
    if name:
        nodename = name
    if nodename[:2] == '\\\\':
        nodename = nodename[2:]
        if not nodename:
            nodename = name
            #raise ValueError( 'invalid node name' )
    if nodename == '.':
        nodename = ''
    if not nodename:
        nodename = win32api.GetComputerName()
    return string.upper( nodename )


def MyRegQueryValue( rootkey, subkeypath, valuename, expectedtype=None ):
    '''
    get a registry value and type, optionally checking for expected type,
    and formatting a nice error message if not found or wrong type
    '''
    hkey = None
    try:
        try:
            hkey = win32api.RegOpenKey( rootkey, subkeypath, RESERVED, win32con.KEY_QUERY_VALUE )
        except win32api.error, err:
            if err[0] == winerror.ERROR_FILE_NOT_FOUND:
                raise KeyError( 'registry key not found' )
            else:
                raise err
                
        try:
            v, t = win32api.RegQueryValueEx( hkey, valuename )
            if expectedtype and t != expectedtype:
                raise TypeError( 'wrong registry value type found: %d expected: %d' % ( t, expectedtype ) )
            return v
        except win32api.error, err:
            if err[0] == winerror.ERROR_FILE_NOT_FOUND:
                raise KeyError( 'registry value not found' )
            else:
                raise err
    except win32api.error, err:
        if err[0] == winerror.ERROR_ACCESS_DENIED: # show user friendly message when access is denied
            raise Exception( 'Registry access denied, please log in as an administrator and try again!' )
        else:
            raise

BUILTINS = ['__repr__', '__str__', '__coerce__', '__getitem__']
BLOCK_TERMINATOR_CHARACTER = ';'
END_OF_DATA_STRING = 'ENDOFDATAENDOFDATAENDOFDATA'
END_OF_DATA = 'PROMPT %s\n' % END_OF_DATA_STRING


# DCOracle may silently crash if you open more than one session to an Oracle database.
# SYOracle emulates a subset of DCOracle. The data returned are tuples, similar to what 
# DCOracle returns, but the data type of the objects returned are always strings.

class Sql:

    def __init__( self, connectString ):
       self.connectString = connectString 
       self.sqlCursor = SqlCursor( self.connectString )
        
    def cursor( self ):
        return self.sqlCursor

    def commit( self ):
        self.sqlCursor.execute( 'commit' )

    def rollback( self ):
        self.sqlCursor.execute( 'rollback' )

    def close( self ):
        self.sqlCursor.close()

    def objects( self, allUsers = 0 ):
        if allUsers <> 0:
            query = 'SELECT object_name, object_type, owner FROM all_objects'
        else:
            query = 'SELECT object_name, object_type FROM user_objects'
        self.sqlCursor.execute( query )
        results = self.sqlCursor.fetchall()
        return results

    def getSource( self, procedure ):
        query = 'SELECT text FROM all_source WHERE name = \'%s\' ORDER BY line'
        self.sqlCursor.execute( query % procedure )
        results = self.sqlCursor.fetchall()
        source = ''
        for ( text, ) in results:
            source = source + text + '\n'
        return source

    def callproc( self, procedureName,  parameters = () ):
        apply( getattr( self.procedures, procedureName), parameters )

    # this deals with stored procedures
    def __getattr__( self, objectName, default = None ):
        if objectName == 'procedures':
            return SqlProcedure( self.connectString )
        else:
            pyType = re.split('\'', str( type(self) ) )[1]
            raise AttributeError, '%s %s has no attribute \'%s\'' % ( 'Sql', pyType, objectName )

Connect = Sql


class SqlProcedure:
    # In DCOracle, you can't prepend the stored procedure name
    # with scope qualifiers. In SYOracle, you can.
    # Stored procedures may be called using this format:
    # x.procedures.databaseLink.schemaName.packageName.procedure()
    # The scope qualifiers in this example - databaseLink.schemaName.packageName
    # are "resolved" one at a time by calling SqlProcedure recursively.
    # Note: No checking as to the validity of the scope qualifiers,
    #       stored procedure name or values passed into the stored procedure
    #       are done.

    def __init__( self, connectString, scope = None ):
        self.connectString = connectString

        # Python doesn't like default values equal to []
        # so default to None and then change to []
        if not scope:
            self.scopes = []
        else: 
            self.scopes = scope


    # For each scope qualifier, __getattr__ 
    # will be called. The name of each scope qualifier
    # is added to a list, and that list is passed into
    # the next instance of SqlProcedure.
    # At the end of it all, will be the procedure name
    # and a list containing all the scope qualifiers in order.
    # At that point, Python calls the __call__ method and the
    # stored procedure will be executed.
    def __getattr__( self, name, default = None ):
        if not name in BUILTINS:
            self.scopes.append( name )
            return SqlProcedure( self.connectString, self.scopes )
            

    def __call__( self, *args ):
        self.cursor = SqlCursor( self.connectString )
        sqlCmd = 'EXECUTE '

        # assemble scope qualifiers as a dot separated string
        for scope in self.scopes:
            sqlCmd = sqlCmd + '%s.' % scope
        sqlCmd = re.sub( '\.$', '', sqlCmd )
        sqlCmd = sqlCmd + '('

        # add in arguments
        for arg in args:
            sqlCmd = sqlCmd + repr( arg ) + ','
        sqlCmd = re.sub( '\,$', '', sqlCmd )
        sqlCmd = sqlCmd + ')'

        # execute the procedure
        self.cursor.execute( sqlCmd )
        self.cursor.close()

   
class SqlCursor:
    def __init__( self, connectString ):
        self.connectString = connectString
        self.data = None
        self.active = TRUE
        self.queryCounter = 0
        self.sqlplusOutput = []
        self.data = []

        # Sqlplus session settings
        self.kColumnSeparator = '<COLSEP>'
        self.kSessionSettings = \
        '''\
            SET FEEDBACK OFF
            SET HEADING OFF
            SET LINESIZE 32767
            SET NUMWIDTH 50
            SET AUTOCOMMIT OFF
            SET COLSEP %s
            SET DEFINE OFF
        ''' % self.kColumnSeparator

        self.kOracleError = 'OracleError'
        self.kSqlplusError = 'SqlplusError'

        cmd = 'SQLPLUS.EXE -silent /nolog'
        sqlplusLogin = 'CONNECT %s' % self.connectString
        sqlplusSettings = sqlplusLogin + '\n' + self.kSessionSettings + '\n'
        sqlplusSettings = sqlplusSettings + END_OF_DATA

        # Start a Sqlplus session and initialize its settings
        ( self.pipeIn, self.pipeOut ) = win32pipe.popen4( cmd, 't' )
        self.pipeIn.write( sqlplusSettings )
        # Flush the output buffer
        self.GetOutput()    
    

    def execute( self, query ):
        if self.active == FALSE:
            raise TypeError( 'The Oracle Connection is closed!' )
        query = string.strip( query )    # remove any trailing returns.
        sqlplusCommand = query + '\n%s\n' % BLOCK_TERMINATOR_CHARACTER 
        sqlplusCommand = sqlplusCommand + END_OF_DATA
        self.pipeIn.write( sqlplusCommand )
        self.GetOutput()
        self.ParseOutput()


    def GetOutput( self ):
        # The Sqlplus session will always output the string ENDOFDATAENDOFDATA...
        # to signal the end of the output. We do not want to read past this string,
        # otherwise we will hang on the output pipe waiting for more data.

        self.sqlplusOutput = []
        endOfData = FALSE
        while endOfData == FALSE:
            line = self.pipeOut.readline()
            if re.search( END_OF_DATA_STRING, line):
                endOfData = TRUE
            else:
                self.sqlplusOutput.append( line )
 
 
    def ParseOutput( self ):
        self.data = []
        for line in self.sqlplusOutput:
            line = re.sub( '\012$', '', line )
            if re.search( '^Input truncated to.*characters$', line ):
                continue
            elif line == 'Connected.':
                continue
            elif line == '':
                continue
            elif re.search( '^ORA-\d*:', line ):              # ORA-NNNNN Oracle error message
                raise Exception( self.kOracleError, line )
            elif re.search( '^SP\d-\d*:', line ):             # SP2-NNNN Sqlplus error message
                raise Exception( self.kSqlplusError, line )
            elif re.search( '^PLS-\d*:', line ):              # PLS-NNNN Sqlplus error message
                raise Exception( self.kSqlplusError, line )

            columns = re.split( self.kColumnSeparator, line )

            if len( columns ) > 0:
                # strip off whitespace around the data
                for i in range( 0, len( columns ) ):
                    columns[ i ] = string.strip( columns[ i ] )
            self.data.append( tuple( columns ) )


    def fetchall( self ):
        allData = self.data
        self.data = []
        return allData


    def fetchmany( self, numRows ):
        someData = []
        for row in range( 0, numRows):
            someData.append( self.fetchone() )
        return someData


    def fetchone( self ):
        if len( self.data ) > 0:
            line = self.data.pop( 0 )
        else:
            line = None
        return line


    def close( self ):
        self.pipeIn.close()
        self.pipeOut.close()
        self.active = FALSE



class Db:
    def __init__( self ):
        user, password, alias = GetAccountInfo.OracleAccountInfo()
        oracleLogin = user + '/' + password + '@' + alias
        self.dbConnection = Connect( oracleLogin )
        self.dbCursor = self.dbConnection.cursor()

    def Query( self, query ):
        self.dbCursor.execute( query )
        return self.dbCursor.fetchall()

    def Execute( self, command ):
        self.dbCursor.execute( command )

    def Commit( self ):
        self.dbConnection.commit()

    def Close( self ):
        self.dbConnection.close()

def Log( message = '\n', printToScreen = TRUE ):
    ( path, file ) = os.path.split( sys.argv[ 0 ] )
    logFile = os.path.join( path, re.sub( '\..*$', '.log', file ) )
    hLogFile = open( logFile, 'a' )
    if printToScreen == TRUE:
        print message
    message = '%s\t %s\n' % ( time.strftime( '%Y %b %d %H:%M:%S', time.localtime( time.time() ) ), message )
    hLogFile.write( message )
    hLogFile.close()

def GetArgs():  
    folderList = []
    moveDuplicates = FALSE

    argc = len( sys.argv )

    i = 1
    while i < argc:
        arg = sys.argv[i]
        argUC = string.upper( arg )
        if argUC in [ '-?', '-H', '-HELP' ]:
            PrintUsageAndExit()

        elif argUC == '-M':
            moveDuplicates = TRUE

        else:
            folderList.append( arg )

        i = i + 1

    return folderList, moveDuplicates
	
def PrintUsageAndExit( errmsg=None, exitcode=EXIT_SUCCESS ):
    if errmsg:
        print errmsg
        print
    print HELP_MESSAGE
    sys.exit( exitcode )


if __name__ == '__main__':
    main()