#!/usr/pkg/bin/python2.7
# -*- coding: Latin-1 -*-
# Name: pytemplate.py
#    This file is part of pytemplate.
#
#    pytemplate 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 version 3.
#
#    pytemplate 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 pytemplate.  If not, see <http://www.gnu.org/licenses/>.
#
# Description:
#        This file is a template to generate scripts in python
#
# Creation date: 23/11/2007
# Updated: 07/10/2008, Version 1.1
#          log function added to log to syslog or option file
#          print function improvment
#
# Updated: 19/10/2008, Version 1.2
#          added some cleanups and moved the docstring
#          moved all nested classes.
#          add some explanation in order to inherits from PyTemplate module
# 	   Suggestions : Benoit Calvez  (benoit@litchis.org)
#
# Updated: 12/11/2008, Version 1.3
#	   added a template for threading in threadtemplate.py and 
#	   a threadexample.py
#
# Updated: 01/12/2008, Version 1.4
#	   optparse replacing getopt giving more flexibility to add
#	   new options
#
# Updated: 11/12/2008, Version 1.5
#	   Daemon facility creation
#	   Bugs correction: setstderr

__version__ = '1.5.1'
__author__ = 'Stephane Bulot'

import sys
import os
import time
import syslog
from optparse import OptionParser
import signal
import threading

class Log(object):
    """log class permits to log script activities
        pkg is needed to log in syslog, the script name info
        logfile: if None, log are done in syslog, else, logs are done in
        logfile given
        In order to log, use self.logger.write('your message')
    """
    def __init__(self,pkg,logfile=None):
        self.pkg=pkg
        if logfile is None:
            self.logtype = 'sys'
            self.logfile = syslog.openlog(self.pkg)
        else:
            self.logtype = 'other'
            self.logfile = open(logfile,'a')
            self.logfilename=logfile
    def write(self,data):
        if self.logtype is 'sys':
            syslog.syslog(data)
        else:
            self.logfile.write(data+'\n')
    def close(self):
        if self.logtype is 'sys':
            syslog.closelog()
        else:
            self.logfile.close()

class UsageException(Exception):
    """Usage exception, raised on bad parameters, or bad option"""
    pass

class PyTemplate(object):
    """
    PyTemplate is a python framework to build scripts with a basic set 
    of development tools.
    THe default usage of pytemplate is to create python classes 
    inherited of the PyTemplate class.
    Has been implemented:
    -Default options (-c for config file, -q for quiet, -l for logging
             -v for version -d daemonize)
    -stdin, stdout, sdterr can be modified on line with  setstdx (in, out, err)
    methods if not daemonized
    -printing functions (lprint and errprint) 
    """

    def __init__(self):
        """ main module init """
        #self.datadir is the directory where will be stored datas
        # By default, it will be $HOME/.nameofscript
        self.datadir = ""
        #You might decide to change stdin, stdout, stderr,
        # By default, they are on sys.stdx
        self.stdin = sys.stdin
        self.stdout = sys.stdout
        self.stderr = sys.stderr
        self.verbose = True 
	# options and arg analysis init
	self.baseoptions()

    def setstdin(self, stdin):
        """setstdin: permits to change default stdin"""
        self.stdin = stdin

    def setstdout(self,stdout):
        """setstdout: permits to change default stdout"""
        self.stdout = stdout

    def setstderr(self,stderr):
        """setstderr: permits to change default stderr"""
        self.stderr = stderr

    def setdatadir(self, dir):
        """setdatadir: permits to change default data directory"""
        if os.path.isdir(dir) is False:
            try:
                os.mkdir(dir)
            except Exception, err:
                self.errprint(str(err))
                raise UsageException("You don't have rights to execute")
        else:
            self.datadir = dir


    def lprint(self,msg,ifnv=False,log=False):
        """ print function
            msg is the message to print (str)
            ifnv: if non verbose, if you put false, message will \
                  be printed only in verbose mode
            log: if True, message will be logged, in log files
        """
        if ifnv is True:
            self.stdout.write(str(msg) + '\n')
        else:
            if self.verbose is True:
                self.stdout.write(str(msg) + '\n')
        if log is True:
            self.logger.write(msg)
        return

    def errprint(self,msg):
        """ print errors function
            msg is the message to print (str)
            error message are always logged
        """
        self.stderr.write(time.strftime('%c') + ': ' + str(msg)+"\n")
        self.logger.write(msg)

    def baseoptions(self):
	""" 
	default options creation for %prog
	implemented options are
	-l/--log filename: log in filename instead of syslog
	-c/--config filename: config file is filename
	-v/--verbose: if present activate verbosity
	-d/--daemonize: if present, process is daemonized
	In order to work, this options need the follwing methods
	load_log(self,args) where args is filename
	load_config(self,args) where args is filename
	load_quiet(self,args) where args is True
	load_daemonize(self,args)

	If you want to add options to %prog, you will need to update
	the self.parser OptionParser instance.
	Here is an example, but you should read optparse doc on
	www.python.org
	1) Add option to self.parser
	self.parser.add_option("f","--foo",help="foo foo options")
	2) Create load_foo(self,args) method, wher args is the -f options
	content.
	
	If you want to add SIGNAL handled by process when daemonize:
	1) Call self.add_signal(newsig) wher nesig is the signal (as 
	described in man signal
	2) Create the method handle_signal_NAME wher NAME is the newsig.
	By default, only SIGHUP and SIGTERM are created. 
	SIGHUP reloads the config file if existing. Iy you need another behaviour,
	create your own handle_signal_SIGHUP(self,sig,frame) method.a
	SIGTERM exits.

	"""
	try:
	    self.parser = OptionParser(version="%prog "+self.version)
	except AttributeError:
	    self.parser= OptionParser() 
	self.parser.add_option("-l", "--log", \
		help="logs are sent to LOG files, if options not used, logs are sent to syslog")
	self.parser.add_option("-c","--config",\
		help="CONFIG is the program configuration file.")
	self.parser.add_option("-q","--quiet",action="store_true",\
		help="Silent mode")
	self.pid =0
        self.parser.add_option("-d","--daemonize",action="store_true",help="daemonize")
        self.signallist=["SIGHUP","SIGTERM","SIGINT"]
        for items in self.signallist:
            if hasattr(self,"handle_signal_"+str(items)):
                signal.signal(getattr(signal,str(items)), \
                getattr(self,"handle_signal_"+str(items)))
            else:
                raise NotImplementedError( \
                       "You must implement method: handle_signal_"+str(items) )

    def add_signal(self,newsignal):
	"""
	add_signal methods permits to add a new signal in the signal list
	handled by process. It you call this function, you will need to create the
	associated handle_signal_SIGNAL method.
	"""
	if newsignal not in self.signallist:
            self.signallist.append(newsignal)
            if hasattr(self,"handle_signal_"+str(newsignal)):
               	signal.signal(getattr(signal,str(newsignal)), \
		getattr(self,"handle_signal_"+str(newsignal)))
            else:
	        raise NotImplementedError( \
			"You must implement method: handle_signal_"+str(items) )

    def load_config(self,args=None):
	""" 
	load_config method is used to load the config file
	given by --config option
	Calls load_fullconfig which should be implemented by
	pytemplate user
	
	"""
	# for portability from 1.3.0
	try:
	    self.loadconfigfile(args)
	except:
	    if args==None:
		if self.configfile != None:
		    self.load_fullconfig(self.configfile)
            else:
		if os.path.isfile(args):
		    self.configfile = args
		    self.load_fullconfig(self.configfile)
		else:	
                   raise UsageException(args +" file does not exist.")

    def load_fullconfig(self,args):
	""" load config file """
	raise NotImplementedError(" You should implement "+\
				"load_fullconfig function")

    def load_log(self,args=None):
	""" 
	load_log method is used to load the log file
	given by --log option
	"""
	self.logger.__init__(self.pkg,args)

    def load_quiet(self,args):
	""" 
	load_verbose method is used to load the log file
	given by --log option
	"""
	if args==True:
		self.verbose = False
	else:
		self.verbose = True

    def load_daemonize(self,args):
        """
        Daemonize the process
        """
        try:
            self.pid = os.fork()
        except OSError, msg:
	    self.errprint("%s [%d]" % (msg.strerror, msg.errno))
	    os._exit(2)

        if self.pid == 0:
            os.setsid()
            try:
                self.pid = os.fork()
            except OSError, msg:
	        self.errprint("%s [%d]" % (msg.strerror, msg.errno))
    	        os._exit(2)

            if (self.pid == 0):
		os.setsid()
	    else:
                os._exit(0)
        else:
            os._exit(0)
	self.stdin.close()
	self.stdout.close()
	self.stderr.close()
	self.setstdin(open("/dev/null","a"))
	self.setstdout(open("/dev/null","a"))
	self.setstderr(open("/dev/null","a"))

	if self.logger.logtype == 'sys':
	    self.logger.close()
	    self.logger = Log(self.pkg)
	else:
	    fname= self.logger.logfilename
	    self.logger.close()
	    self.logger = Log(self.pkg,fname)
        return self.pid

    def loadparams(self,args):
	"""
	loads arguments that are not options	
	"""
	raise NotImplementedError("You must implement loadparams method.")

    def handle_signal_SIGHUP(self,sig,egg):
        if hasattr(self,"load_config"):
            self.load_config()

    def handle_signal_SIGTERM(self,sig,egg):
            sys.exit()

    def handle_signal_SIGINT(self,sig,egg):
            sys.exit()

    def main(self, argv=None):
        """ argv are sys arguments by default, but can be modified in 
            the main call
        """
        raise NotImplementedError("Your program must implements PyTemplate's main function")

    def run(self, argv=None):
        """So run just checks the params, the config and co, and then
        runs self.main() which is normally implemented
        """

        if argv is None:
            self.argv = sys.argv
        else:
            self.argv = argv
        # self.pkg is the name of the script """
        self.pkg = os.path.basename(self.argv[0])
        # data directory is set on $HOME/.nameofscript """
        self.setdatadir(os.environ['HOME']+"/."+self.pkg)
	self.conffile= self.datadir+"/"+self.pkg+".conf"
        # logging function initialzed by defauld on syslog """
        self.logger = Log(self.pkg)
	(options,args)=self.parser.parse_args(self.argv)
	for items in self.parser.option_list:
		if items.dest:
			if getattr(options,items.dest) != None :
				if hasattr(self,"load_"+items.dest):
					getattr(self,"load_"+items.dest)\
						(getattr(options,items.dest))
				else:
					raise NotImplementedError( \
			"You must implement load_"+items.dest +" method."  )
        self.loadparams(args)
	self.main()


class ThreadListTemplate(list):
	''' ThreadListTemplate is a class inherited from list type. 
	This class should be used to manage local thread (not main thread)
	append function will start thread
	remove function will stop and remove thread
	stop will stop al thread
	'''
	def __init__(self):
		super(ThreadListTemplate,self).__init__()

	def __call__(self,thread,flag="start"):
		if flag=="start":
			thread.start()
			self.append(thread)
		elif flag=="stop":
			thread.stop()
			self.remove(thread)
		else: 
			raise ThreadListError(self,thread)
		return

	def stop(self):
		""" stop all thread from the list
		"""
		for items in self:
			items.stop()
		self.list=[]	

	class ThreadListError(Exception):
		def __init__(self,local,thread):
			self.local= local
			self.thread=thread
			for items in self.local:
				items.stop()
		def __str__(self):
			return str(self.thread) + " is not a thread. All thread in list are stopped"

class ThreadTemplate(threading.Thread):
	""" ThreadTemplate is a simple class inherited from threading.Thread
	It should be used in order to create thread easely, by just 
	creating the execute function  called each sleeptime by run
	ThreadTemplate implements a embedded stop function
	__ini__ should be overloaded in order to add some initializations.
	"""
	def __init__(self,sleeptime):
		threading.Thread.__init__(self)
		self.sleeptime=sleeptime
		self.stopev=threading.Event()

	def run(self):
		while not self.stopev.isSet():
			time.sleep(self.sleeptime)	
			self.execute()

	def stop(self):	
		""" stop will stop the execution of the thread """
		self.stopspecif()
		self.stopev.set()
	
	def stopspecif(self):
		""" should be used to make specific command while stopping
		"""
		return

	def execute(self):
		return

	class ThreadError(Exception):
		""" When an exception in the thread is raised, thread is stopped
		"""
		def __init__(self,local,err=""):
			self.local=local
			self.local.stop()

		def __str__(self):
			return self.local.__class__.__name__+' error: '
		
if __name__ == "__main__":
    sys.exit(mainmodule().main())

