# -*- coding: utf-8 -*-

""" Gestion de lock

Copyright (C) Frédéric Pauget
Licence : GPLv2
"""

import os,string,time,sys, affich_tools
from commands import getoutput
from user_tests import getuser
from fcntl import lockf, LOCK_EX, LOCK_NB, LOCK_UN
import errno, random

def wait_lock(lock_name, lock_comment='', d=None, retry=0.2):
    """Attend la disponibilité d'un lock en utilisant le framework Twisted.

    Si d est de type Deferred, on est en mode asynchrone.
    retry permet de réessayer dans `retry' secondes.
    """
    from twisted.internet import reactor, defer
    try:
        try:
            make_lock(lock_name, lock_comment, nowait=1, quiet=True)
        except AssertionError:
            raise
        except:
            print "*** Probleme lors de l'obtention du lock..."
            import traceback
            traceback.print_exc()
            # On a sans doute pas le lock et c'est pas moi qui me tape
            # à debugguer les trucs à Fred.
            raise AssertionError

        # On a le lock
        if not d:
            # On est en mode synchrone
            return defer.succeed(None)
        # On appelle le callback car on est en mode asynchrone
        print "We got the lock, in asynchronous mode"
        d.callback(None)
    except AssertionError:
        # On a pas le lock
        if not d:
            # On est en mode synchrone, on va passer en asynchrone
            d = defer.Deferred()
        # On essaie de nouveau plus tard
        newretry = min(retry * 2, 4) + random.random() * 0.1 * retry
        reactor.callLater(retry, wait_lock, lock_name, lock_comment, d, newretry)
        return d

def make_lock(lock_name, lock_comment='',nowait=0, quiet=False) :
    """ Création d'un lock
    si nowait=1 fait un sys.exit(254) quand un ancien lock actif est rencontré
    """
    return
    lock_dir = '/var/lock/gestion'
    try:
        os.mkdir(lock_dir)
    except OSError:
        pass
    lock_file = "%s/%s" % (lock_dir, lock_name)

    # On créé une zone d'exclusion
    lock_fd_dl=open("%s-dotlock" % lock_file, "w")
    # On demande un verrou exclusif
    try:
        lockf(lock_fd_dl, LOCK_EX | LOCK_NB)
    except IOError, e:
        if e.errno not in [errno.EACCES, errno.EAGAIN]:
            raise
        if nowait:
            if quiet:
                # On va plutot lever une exception
                raise AssertionError('In critical section')
            else:
                sys.stderr.write('\tpropriétaire : inconnu\n\tpid : inconnu\n\tdémarré depuis inconnu\n')
                sys.exit(254)
        else:
            # La procédure de lock est deja en cours d'execution, on essaie un peu plus tard
            time.sleep(0.5)
            # On enleve le verrou système
            lockf(lock_fd_dl, LOCK_UN)
            lock_fd_dl.close()
            return make_lock(lock_name, lock_comment)

    if os.path.isfile(lock_file) :
        ### Lock existant

        # Lecture du lock
        fd = open(lock_file, "r")
        pid= fd.readline().strip()
        user = fd.readline().strip()
        fd.close()

        # Informations sur le processus lockant
        if os.system( "ps ax | grep -q '^%s '" % pid ) :
            # Le script lockant ne tourne plus
            os.remove(lock_file)
        elif nowait :
            if not quiet:
                sys.stderr.write('Lock : %s\n' % lock_file)
            l=getoutput("ps ax -o pid,etime | awk '($1 == %s)'" % pid)
            data = [ user , pid , l.strip() ]

            # Formatate de etime
            s = data[-1].split('-')
            if len(s)==2 :
                txt = '%s jour(s) ' % s[0]
                s=s[1]
            else :
                txt = ''
                s=s[0]

            s = s.split(':')
            if len(s) == 3 :
                txt = '%sh%smin%ss' % tuple(s)
            elif len(s) == 2 :
                txt = '%smin%ss' % tuple(s)
            else :
                txt = '???'

            data[-1]=txt

            if not quiet:
                sys.stderr.write('\tpropriétaire : %s\n\tpid : %s\n\tdémarré depuis %s\n' % tuple(data) )
                sys.exit(254)
            else:
                # On va plutot lever une exception
                raise AssertionError(tuple(data))
        else :
            # Il faut attendre
            a = affich_tools.anim('\tattente du lock')
            for i in range(8) :
                time.sleep(1)
                a.cycle()
            sys.stdout.write('\r')
            # On enleve le verrou système
            lockf(lock_fd_dl, LOCK_UN)
            lock_fd_dl.close()
            return make_lock(lock_name, lock_comment)

    ### Prise du lock
    lock_fd = file(lock_file,"w")
    try:
        utilisateur = getuser()
    except:
        utilisateur = "inconnu"
    lock_fd.write("%s\n%s\n%s" % (os.getpid(), utilisateur, lock_comment) )
    lock_fd.close()

    # On enleve le verrou système
    lockf(lock_fd_dl, LOCK_UN)
    lock_fd_dl.close()


def remove_lock( lock_name ) :
    """ Destruction du lock """
    return
    # On créé une zone d'exclusion
    lock_dir = '/var/lock/gestion'
    lock_file = "%s/%s" % (lock_dir, lock_name)

    lock_fd_dl=open("%s-dotlock" % lock_file, "w")
    # On demande un verrou exclusif
    try:
        lockf(lock_fd_dl, LOCK_EX | LOCK_NB)
    except IOError, e:
        if e.errno not in [errno.EACCES, errno.EAGAIN]:
            raise
        # Déjà locké
        time.sleep(0.5)
        return remove_lock(lock_name)
    try :
        fd = open(lock_file, "r")
        if fd.readline().strip()=="%s" % os.getpid():
            os.remove(lock_file)
            fd.close()
    except :
        pass

    # On enleve le verrou système
    lockf(lock_fd_dl, LOCK_UN)
    lock_fd_dl.close()
