#! /usr/bin/env python

#############################################################
#                                                           #
#   Author: Sandrine Larroude                               #
#   Organization:'Biological Software and Databases' Group, #
#                Institut Pasteur, Paris.                   #
#   Distributed under GPLv2 Licence. Please refer to the    #
#   COPYING.LIB document.                                   #
#                                                           #
#############################################################

import os, sys, time, shutil, getopt
from datetime import datetime

#append Mobyle Home to the search modules path
MOBYLEHOME = None
if os.environ.has_key('MOBYLEHOME'):
    MOBYLEHOME = os.environ['MOBYLEHOME']
if not MOBYLEHOME:
    sys.exit('MOBYLEHOME must be defined in your environment')

if ( os.path.join( MOBYLEHOME , 'Src' ) ) not in sys.path:
    sys.path.append( os.path.join( MOBYLEHOME , 'Src' ) )


import Mobyle.ConfigManager
import Mobyle.Session
import Mobyle.Utils


""" Cleaner of jobs and anonymous sessions """

def cleanjob(config, delay, quiet, ctime, log= None):
    
    path = config.results_path()
    er = 0
    
    if os.path.isdir(path):
        
        jobres = os.listdir(path)
        # ADMINDIR is excluded of the directory list because it is not concerned by the cleaning
        if os.path.isdir(os.path.join(path,"ADMINDIR")):
            jobres.remove("ADMINDIR")
        for p in jobres:
            pp = os.path.join(path, p)
            # to exclude the possible files existing in the directory
            if not os.path.isdir(pp):	continue
            jobdir = os.listdir(pp)
            for q in jobdir:
                qp = os.path.join(pp, q)
                # to exclude the possible files existing in the directory
                if not os.path.isdir(qp): 	continue
                # extraction of the job status
                try:
                    jobstat = Mobyle.Utils.Admin(qp)
                except Mobyle.MobyleError.MobyleError, e:
                    er = 1
                    msg = "Error with this job directory which cannot be cleaned %s --> %s" % (qp, e)
                    logguer(msg, log, True)
                    continue
                os.path.dirname(p)

                jobstatus= jobstat.getStatus()
                #to prevent when getStatus returns Unknown status
                if not jobstatus.isKnown():
                    msg = "Unknown status in this job directory --> %s " % qp
                    logguer(msg, log, quiet)
                    continue
                else: 
                    # to retrieve the last modification time
                    mtime = os.path.getmtime(qp)
                    # test if the job is erasable or not
                    if jobstatus.isEnded() and int( ctime - mtime ) > delay:
                        try:
                            shutil.rmtree(qp)
                        except OSError, e:
                            er = 1
                            msg = "Error with this job directory which cannot be cleaned %s --> %s" % (q, e)
                            logguer(msg, log, quiet)
                    #Case of "lost" pending, hold and running status jobs
                    elif jobstatus.isQueryable() and int( ctime - mtime ) > delay:
                        #if the job still has the hold or pending status after the delay --> WARNING
                        if jobstatus.code != 3:
                            msg = "WARNING - This job %s has the status of %s since more than the delay" % (qp, jobstatus)
                            logguer(msg, log, quiet)
                        else:
                            #if the running status job is not anymore in the ADMINDIR directory --> ERROR
                            if not os.access(os.path.join(path,"ADMINDIR",p+"."+q), os.F_OK):
                                msg = "ERROR - This job %s has the 'running' status even if it is ended" % (qp)
                                logguer(msg, log, quiet)
                    #Case of "lost" building or submitted status jobs
                    elif int( ctime - mtime ) > delay:
                       msg = "ERROR - This job %s has the %s status since more than the delay" % (qp, jobstatus)
                       logguer(msg, log, quiet)
                       

    else:           
        er = 1
        msg = "The job result path does not exist. Please check your RESULTS_PATH in the conf file."
        logguer(msg, log, quiet)
        
    return er

	
def cleansession(config, delay, quiet, ctime, log= None):
    
    path = config.user_sessions_path()
    er = 0
    
    if os.path.isdir(path):
        
        if config.anonymousSession() != "no":
            path = os.path.join(path, "anonymous")
            session_a = os.listdir(path)
            for s in session_a:
                sp = os.path.join(path, s)
                # to exclude the possible files existing in the directory
                if not os.path.isdir(sp):	continue  
                mtime = os.path.getmtime(sp)
                # To get the jobs attached to this session
                j = Mobyle.Session.Session(sp, s, config)
                try:
                    jobs = j.getAllJobs()
                    # test if the anonymous session is erasable or not
                    if not jobs and int(ctime - mtime) > delay:
                        try:
                            shutil.rmtree(sp)
                        except OSError, e:
                            er = 1
                            msg = "Error with this anonymous session directory which cannot be cleaned %s --> %s" % (s, e)
                            logguer(msg, log, quiet)
                            continue
                except Mobyle.MobyleError.SessionError, m:  
                    # remove a session directory without ".session.xml"
                    if not os.access(os.path.join(sp,'.session.xml'), os.F_OK):
                        logguer("There is no .session.xml in this anonymous session %s" %  s,log, True) # no need to log this in stderr
                        shutil.rmtree(sp)
                    else:
                        er = 1
                        msg = "Mobyle error with this anonymous session %s --> %s" % (s, m)
                        logguer(msg, log, quiet)
                    continue

    else:
        er = 1
        msg = "The session path does not exist. Please check your USER_SESSIONS_PATH in the conf file."
        logguer(msg, log, quiet)
    
    return er


def logguer(msg, log= None, quiet= False):
    
    if log:
        print >>log, "%s " % datetime.now().strftime("%a, %d %b %Y %H:%M:%S") + msg    
    if not quiet:
        print >>sys.stderr, msg


if __name__ == "__main__":
    
    config = Mobyle.ConfigManager.Config()
    ct = time.time() # current time
    
    # Part to configure the function call by command line
    opts, args = None, None
    message = """
    Usage : mobclean [OPTION]
    Clean Result or Anonymous sessions of Mobyle (Result by default)

    options:
        -J          Clean Job Results.
        -S          Clean anonymous sessions.
        -d <val>    Delete directories older than <val> days ( positive integer value ).
        -h          Display this help and exit.
        -q          Without error messages (verbose).
        -l <val>    path to the Logfile where put the logs
        
    examples:
        mobclean 	--> By default, the cleaning of jobs & sessions is done
        mobclean -S -d 2
        mobclean -J -S
        mobclean --help
    """
    # By default, delay is the one of the config file
    d = config.remainResults()
   
    try:
        opts, args = getopt.getopt(sys.argv[1:], "JShqd:l:", ["job", "session", "help", "quiet", "delay=", "logfile="])
    except getopt.GetoptError:
        print message
        sys.exit(1)
    
    session, job, q, ers, erj, logfile = False , False , False, 0, 0, None

    for opt, val in opts:
        if opt in ( "-h","--help"):
            print message
            sys.exit(0)
        if opt in ( "-d", "--delay" ):
            d = val
        elif opt in ( "-J" , "--job"):
            job = True
        elif opt in ( "-S", "--session" ):
            session = True
        elif opt in ( "-q", "--quiet" ):
            q = True
        elif opt in ( "-l", "--logfile" ):
            try:
                logfile = open (os.path.abspath(val) , "a")
            except:
                print >>sys.stderr,"No logs available for this run.Error trying to open %s \n"% l
         
    # By default, the cleaning of jobs & sessions is done
    if not (job and session):
        job, session = True, True
        
    try:
        d = int(d)
        if d < 0:
            print message
            sys.exit(1)
        else:
            d = d * 24 * 60 * 60  # delay conversion from day to second
    except ValueError:
        print >>sys.stderr,"The number of days is invalid. Check that it is an integer.\n" + message
        sys.exit(1)
        
    try:      
        if job:
            erj = cleanjob(config, delay=d, quiet=q, ctime=ct, log = logfile)
        if session:
            ers = cleansession(config, delay=d, quiet=q, ctime=ct, log = logfile)
    finally:
        if logfile:
            logfile.close()
        
    sys.exit (erj or ers)
    
