|
|
import os
|
|
|
|
|
|
|
|
|
class LockFile(object):
|
|
|
"""Provides methods to obtain, check for, and release a file based lock which
|
|
|
should be used to handle concurrent access to the same file.
|
|
|
|
|
|
As we are a utility class to be derived from, we only use protected methods.
|
|
|
|
|
|
Locks will automatically be released on destruction"""
|
|
|
__slots__ = ("_file_path", "_owns_lock")
|
|
|
|
|
|
def __init__(self, file_path):
|
|
|
self._file_path = file_path
|
|
|
self._owns_lock = False
|
|
|
|
|
|
def __del__(self):
|
|
|
self._release_lock()
|
|
|
|
|
|
def _lock_file_path(self):
|
|
|
""":return: Path to lockfile"""
|
|
|
return "%s.lock" % (self._file_path)
|
|
|
|
|
|
def _has_lock(self):
|
|
|
""":return: True if we have a lock and if the lockfile still exists
|
|
|
:raise AssertionError: if our lock-file does not exist"""
|
|
|
if not self._owns_lock:
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
def _obtain_lock_or_raise(self):
|
|
|
"""Create a lock file as flag for other instances, mark our instance as lock-holder
|
|
|
|
|
|
:raise IOError: if a lock was already present or a lock file could not be written"""
|
|
|
if self._has_lock():
|
|
|
return
|
|
|
lock_file = self._lock_file_path()
|
|
|
if os.path.isfile(lock_file):
|
|
|
raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file))
|
|
|
|
|
|
try:
|
|
|
fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0)
|
|
|
os.close(fd)
|
|
|
except OSError,e:
|
|
|
raise IOError(str(e))
|
|
|
|
|
|
self._owns_lock = True
|
|
|
|
|
|
def _obtain_lock(self):
|
|
|
"""The default implementation will raise if a lock cannot be obtained.
|
|
|
Subclasses may override this method to provide a different implementation"""
|
|
|
return self._obtain_lock_or_raise()
|
|
|
|
|
|
def _release_lock(self):
|
|
|
"""Release our lock if we have one"""
|
|
|
if not self._has_lock():
|
|
|
return
|
|
|
|
|
|
# if someone removed our file beforhand, lets just flag this issue
|
|
|
# instead of failing, to make it more usable.
|
|
|
lfp = self._lock_file_path()
|
|
|
try:
|
|
|
# on bloody windows, the file needs write permissions to be removable.
|
|
|
# Why ...
|
|
|
if os.name == 'nt':
|
|
|
os.chmod(lfp, 0777)
|
|
|
# END handle win32
|
|
|
os.remove(lfp)
|
|
|
except OSError:
|
|
|
pass
|
|
|
self._owns_lock = False
|
|
|
|