#
# YodaMonitor()
# author  Maurizio Nagni
# version 1.0 - 21 September 2005
#
# This is the main class for the managements of the Yoda monitoring.
#
# Parameters:
# - scriptHost     The directory where the ROOT scritps are stored  (required)
# - pitHost        The monitored directory for "new" files          (required)
# - sourceHost     The directory where to the "new" files are moved (optional)
#

import os, sys, time, ROOT, traceback, signal
import _fam
from ngn.pamela.process.YodaProcess import YodaProcess
from ConfigParser import *
from ngn.utils.benchmark import Benchmark

class YodaMonitor:
    def __init__(self, logger, configFile):
	self.__logger 		= logger
	self.__configFile 	= configFile
	self.__config 		= ConfigParser()
	self.__config.read(configFile)
        self.__conn	        = None
	self.__folderToMonitor	= ''
        self.__benchmarkOn      = False

	try:
	    if (self.__config.has_option('YodaMonitor', 'folderToMonitor')):
                self.__folderToMonitor	= self.__config.get('YodaMonitor', 'folderToMonitor')

	    if (self.__config.has_option('YodaMonitor', 'benchmarkFile')):
                self.__bench = Benchmark(self.__config.get('YodaMonitor', 'benchmarkFile'))
                self.__benchmarkOn      = True

            if (self.__config.has_option('YodaMonitor', 'dbConnectionFile') and (os.path.exists(self.__config.get('YodaMonitor', 'dbConnectionFile')))):
                # In case of a database connection install the
                self.__conn = self.__doConnection(self.__config.get('YodaMonitor', 'dbConnectionFile'))

            if (self.__config.has_option('YodaMonitor', 'pollingDelay') and (os.path.exists(self.__config.get('YodaMonitor', 'pollingDelay')))):
                self.__pollingDelay = self.__config.get('YodaMonitor', 'pollingDelay')
            else:
                self.__pollingDelay = 60
	
	except NoSectionError, detail:
		print 'Section missing in configuration file: ', detail
		sys.exit(1)
	except NoOptionError, detail:
		print 'Wrong option in configuration file: ', detail
		sys.exit(1)
	
	if ((self.__folderToMonitor != '') and (self.__conn != None)):
		self.__logger.info('Check your configuration file. You should not start both folderMonitor and DBMonitor. In this case only DBMonitoring will start.')
		
    def __del__(self):
        if (self.__conn != None):
            self.__conn.Close()
            if (self.__benchmarkOn):
                self.__bench.close()

    def execute(self):
	# The ConnectionFile exists?
	if (self.__conn != None):
            self.__monitorDB()
	elif (self.__folderToMonitor != ''):
	    self.__monitorFolder()

    def __monitorFolder(self):
    #*****************************************************************************
    #*  FAMEvent Structure:
    #*
    #*  When a file/directory has been modified/deleted, fam will pass
    #*  a FAMEvent structure to the application (through the callback
    #*  in the FAMMonitorFile/Directory routines).  This structure will
    #*  describe what event happened to the file.  The different events
    #*  that can happen to a file are listed in the FAMCodes enum.
    #****************************************************************************
    # enum FAMCodes { FAMChanged=1, FAMDeleted=2, FAMStartExecuting=3, 
    #		FAMStopExecuting=4, FAMCreated=5, FAMMoved=6, 
    #		FAMAcknowledge=7, FAMExists=8, FAMEndExist=9 };
    #
    # typedef struct  FAMEvent {
    #    FAMConnection* fc;         /* The fam connection that event occurred on */
    #    FAMRequest fr;             /* Corresponds to the FamRequest from monitor */
    #    char *hostname;            /* host and filename - pointer to which */
    #    char filename[PATH_MAX];   /* file changed */
    #    void *userdata;            /* userdata associated with this monitor req. */
    #    enum FAMCodes code;        /* What happened to file - see above */
    # } FAMEvent;
    #
    #*/
      #--- Setup a monitor for new files into the pitHost directory ----
      signal.signal(signal.SIGCHLD, signal.SIG_IGN)
      conn          = _fam.open()
      request       = conn.monitorDirectory(self.__folderToMonitor, None)
      # Infinite loop waiting for new DAT file
      while True:
          event = conn.nextEvent()
          # has been created a new file?
          if event.code == 5:
              fileName = event.filename
              uploadedSize = 0
              time.sleep(5)
              self.__logger.info('Uploading ' + self.__folderToMonitor + '/' + fileName)
	      if (os.path.isdir(fileName)): continue
              while ((os.path.getsize(self.__folderToMonitor + '/' + fileName) - uploadedSize) > 0):
                  uploadedSize = os.path.getsize(self.__folderToMonitor + '/' + fileName)
                  time.sleep(5)
              if (not os.path.isdir(fileName)):
                 #start thread
                 #-------------------------------
                 if (self.__benchmarkOn):
                       self.__bench.start(self.__folderToMonitor + '/', fileName, 'YODA')
                 self.__logger.info('Starting YodaProcess for' + self.__folderToMonitor + '/' + fileName)
                 newProcess = YodaProcess(self.__folderToMonitor, self.__configFile, fileName, self.__logger)
                 newProcess.start()
                 while newProcess.isAlive(): pass
                 if (self.__benchmarkOn):
                     self.__bench.end()
                 #--------------------------------
                 #end thread
              else :
                  self.__logger.info('File not exist or not suitable')
                  #sys.stdout.flush()
                  #os.remove(self.__pitHost + fileName)
	


    def __monitorDB(self):
       while True:
           time.sleep(self.__pollingDelay)
           result = self.__conn.Query("""SELECT path, cln2_name FROM FILES WHERE proces_flag = 1""")
	   row = result.Next()
           
    	   while (not (row == None) and not (row == 0)):
               fileName = row.GetField(0) + row.GetField(1)
               if (os.path.exists(fileName)):
                   #start thread
                   #-------------------------------
                   if (self.__benchmarkOn):
                       self.__bench.start(row.GetField(0) + '/', row.GetField(1), 'yodaMonitor')
                   sourceHost = row.GetField(0)
                   fileName  = row.GetField(1)
                   self.__logger.info("Starting new YodaProcess for file '%s' .\n", sourceHost + '/' + fileName)
		   newProcess = YodaProcess(sourceHost, self.__configFile, fileName, self.__logger)
                   newProcess.start()
                   while newProcess.isAlive(): pass
                   self.__logger.info("Terminated YodaProcess for file '%s' .\n", fileName)
                   self.__conn.Query('UPDATE FILES SET proces_flag = 2 WHERE path = \'' +  sourceHost + '\' AND cln2_name = \'' + fileName + '\'')
                   self.__logger.info('UPDATE FILES SET proces_flag = 2 WHERE path = \'' + sourceHost  + '\' AND cln2_name = \'' + fileName + '\'')
                   if (self.__benchmarkOn):
                       self.__bench.end()
                   #--------------------------------
                   #end thread
               row = result.Next()

    def __doConnection(self, dbConf):
		dbConfig = ConfigParser()
		dbConfig.read(dbConf)
		if (dbConfig.has_option('db', 'host') and dbConfig.has_option('db', 'user') and dbConfig.has_option('db', 'password')):
			try:
				return ROOT.TSQLServer.Connect(dbConfig.get('db', 'host'), dbConfig.get('db', 'user'), dbConfig.get('db', 'password'))                      
			except NoSectionError, detail:
				raise Exception('Section missing in configuration file: ', detail)
			except NoOptionError:
				raise Exception ('Wrong option in configuration file: ', detail)

