"""

Functions:
lwrite   Write to a handle, locking it to prevent concurrent writing.
tswrite  Write to a handle with a timestamp.
openfh   Open a file name or handle.

Classes:
LockedHandle  A handle that is locked to protect concurrent writing.

"""
def _lock(handle):
    import fcntl
    import time
    # There are problems with lockf on NFS right now.  Switch to a
    # different locking mechanism.
    from Extracto import simplelock

    fileno = handle.fileno()
    start = time.time()
    while 1:
        try:
            #fcntl.lockf(fileno, fcntl.LOCK_EX)
            simplelock.acquire("lwrite.%d" % fileno)
        except Exception, x:
            if str(x).find('No record locks available') < 0 or \
               time.time() >= start + 600:   # try to lock for 10 minutes
                raise
        else:
            break

def _unlock(handle):
    import fcntl
    from Extracto import simplelock
    
    fileno = handle.fileno()
    #fcntl.lockf(fileno, fcntl.LOCK_UN)
    simplelock.release("lwrite.%d" % fileno)

def lwrite(s, handle=None):
    if handle is None:
        import sys
        handle = sys.stdout
    _lock(handle)
    try:
        handle.write(s)
        handle.flush()
    finally:
        _unlock(handle)

def nlwrite(s, handle=None):
    if handle is None:
        import sys
        handle = sys.stdout
    handle.write(s)
    
def tswrite(s, handle=None, format="%m/%d/%Y %H:%M:%S", write_fn=lwrite):
    import time
    time_tup = time.localtime(time.time())
    now = time.strftime(format, time_tup)
    write_fn("%s %s" % (now, s), handle=handle)

class LockedHandle:
    def __init__(self, handle, bufsize=4096):
        assert bufsize >= 0
        self.handle = handle
        self.buffer = []
        self.buflen = 0
        self.bufsize = bufsize
    def write(self, s):
        self.buffer.append(s)
        self.buflen += len(s)
        if self.buflen >= self.bufsize:
            self.flush()
    def close(self):
        self.flush()
        self.handle.close()
    def fileno(self):
        return self.handle.fileno()
    def flush(self):
        if self.buffer:
            lwrite(''.join(self.buffer), handle=self.handle)
            self.buffer = []
            self.buflen = 0
    def __del__(self):
        self.flush()

def openfh(file_or_handle, mode='r'):
    if type(file_or_handle) is not type(''):
        # If this is not a string, assume it's a already a file
        # handle.
        return file_or_handle
    if file_or_handle.endswith(".gz"):
        import gzip
        return gzip.open(file_or_handle, mode)
    return open(file_or_handle, mode)
