##########################################################
## stolen and (slighty) adapted from:
##  http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
##

import os, time

if os.name == 'nt':
    import msvcrt
elif os.name == 'posix':
    import fcntl
    LOCK_EX = fcntl.F_WRLCK
    LOCK_SH = fcntl.F_RDLCK
    LOCK_NB = fcntl.F_UNLCK
else:
    raise RuntimeError("Locker only defined for nt and posix platforms")

if os.name == 'nt':
    def lock(file):
        """
        Lock first 10 bytes of a file.
        """ 
        pos = file.tell() # remember current position
        file.seek(0)
        # By default, python tries about 10 times, then throws an exception.
        # We want to wait forever.
        acquired = False
        while not acquired:
            try:
                msvcrt.locking(file.fileno(),msvcrt.LK_LOCK,10)
                acquired = True
            except IOError, x:
                if x.errno != 36: # 36, AKA 'Resource deadlock avoided', is normal
                    raise
        file.seek(pos) # reset position

    def unlock(file):
        """
        Unlock first 10 bytes of a file.
        """ 
        pos = file.tell() # remember current position
        file.seek(0)
        msvcrt.locking(file.fileno(),msvcrt.LK_UNLCK,10)
        file.seek(pos) # reset position

elif os.name =='posix':
    import socket, errno

    def _tmpFileName(fileName):
        return "%s.%s.%d" % ( fileName, socket.gethostname(), os.getpid() )
    def _lckFileName(fileName):
        return "%s.lock" % fileName

    def _linkCount( lockFileName ):
        try:
            return os.stat(lockFileName).st_nlink
        except OSError, e:
            if e.errno != errno.ENOENT:
                raise
            return -1
    def _read(fileName):
        try:
            fp = open(fileName)
            try:     readFileName = fp.read()
            finally: fp.close()
            return readFileName
        except EnvironmentError, e:
            if e.errno != errno.ENOENT:
                raise
            return None
            
    def _sleep(): time.sleep(8)

    def lock(file):
        fileName = file.name
        tmpFileName = _tmpFileName(fileName)
        fp = open( tmpFileName, "w" )
        fp.write( tmpFileName )
        fp.close()

        lockFileName = _lckFileName( fileName )
        while True:
            if _linkCount(lockFileName) != -1: _sleep()
            try:
                os.link( tmpFileName, lockFileName )
                # we acquired the lock
                return
            except OSError, e:
                if e.errno == errno.ENOENT:
                    pass
                elif e.errno != errno.EEXIST:
                    os.unlink(tmpFileName)
                    raise
                elif _linkCount( lockFileName ) != 2:
                    pass
                elif _read(lockFileName) == tmpFileName:
                    raise
                else:
                    # someone else owns that lock
                    pass
            ## didn't get the lock !!
            ## say something ?
            #print _id_()," failed to acquire the lock ..."
            _sleep()
            pass
        return
    
    def unlock(file):
        fileName = file.name
        tmpFileName  = _tmpFileName(fileName)
        lockFileName = _lckFileName( fileName )

        try:
            os.unlink( lockFileName )
        except OSError, e:
            if e.errno != errno.ENOENT:
                raise
        # remove the tmp file
        try:
            os.unlink( tmpFileName )
        except OSError, e:
            if e.errno != errno.ENOENT:
                raise
        return

import logging
## Lock a file.
#  The file for the lock is created if it doesn't exists and it the "temporary"
#  argument is set to True it will also be deleted when the lock is not needed.
#  The unlocking is done in the destructor (RAII pattern).
class LockFile(object):
    def __init__(self, name, temporary = False):
        self.name = name
        self.temporary = temporary
        self.file = None
        self.log = logging.getLogger("LockFile")
        self.log.info("%s - Locking on %s", time.strftime("%Y-%m-%d_%H:%M:%S"), self.name)
        if not os.path.exists(name):
            mode = "w"
        else:
            self.temporary = False # I do not want to delete a file I didn't create
            mode = "r+"
        try:
            self.file = open(self.name, mode)
            lock(self.file)
        except:
            self.log.warning("Cannot acquire lock on %s", self.name)
    def __del__(self):
        if self.file:
            unlock(self.file)
            self.file.close()
            if self.temporary:
                try:
                    os.remove(self.name)
                except:
                    pass
            self.log.info("%s - Lock on %s released", time.strftime("%Y-%m-%d_%H:%M:%S"), self.name)