mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-01 00:43:37 +00:00
05410e2aa0
Replaced urllib2 with requests for both TVDB and TVRage. Updated cache cleanup code to process both TVDB and TVRage cache folders.
317 lines
8.4 KiB
Python
317 lines
8.4 KiB
Python
"""
|
|
lockfile.py - Platform-independent advisory file locks.
|
|
|
|
Requires Python 2.5 unless you apply 2.4.diff
|
|
Locking is done on a per-thread basis instead of a per-process basis.
|
|
|
|
Usage:
|
|
|
|
>>> lock = LockFile('somefile')
|
|
>>> try:
|
|
... lock.acquire()
|
|
... except AlreadyLocked:
|
|
... print 'somefile', 'is locked already.'
|
|
... except LockFailed:
|
|
... print 'somefile', 'can\\'t be locked.'
|
|
... else:
|
|
... print 'got lock'
|
|
got lock
|
|
>>> print lock.is_locked()
|
|
True
|
|
>>> lock.release()
|
|
|
|
>>> lock = LockFile('somefile')
|
|
>>> print lock.is_locked()
|
|
False
|
|
>>> with lock:
|
|
... print lock.is_locked()
|
|
True
|
|
>>> print lock.is_locked()
|
|
False
|
|
|
|
>>> lock = LockFile('somefile')
|
|
>>> # It is okay to lock twice from the same thread...
|
|
>>> with lock:
|
|
... lock.acquire()
|
|
...
|
|
>>> # Though no counter is kept, so you can't unlock multiple times...
|
|
>>> print lock.is_locked()
|
|
False
|
|
|
|
Exceptions:
|
|
|
|
Error - base class for other exceptions
|
|
LockError - base class for all locking exceptions
|
|
AlreadyLocked - Another thread or process already holds the lock
|
|
LockFailed - Lock failed for some other reason
|
|
UnlockError - base class for all unlocking exceptions
|
|
AlreadyUnlocked - File was not locked.
|
|
NotMyLock - File was locked but not by the current thread/process
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import sys
|
|
import socket
|
|
import os
|
|
import threading
|
|
import time
|
|
import urllib
|
|
import warnings
|
|
import functools
|
|
|
|
# Work with PEP8 and non-PEP8 versions of threading module.
|
|
if not hasattr(threading, "current_thread"):
|
|
threading.current_thread = threading.currentThread
|
|
if not hasattr(threading.Thread, "get_name"):
|
|
threading.Thread.get_name = threading.Thread.getName
|
|
|
|
__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
|
|
'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
|
|
'LinkLockFile', 'MkdirLockFile', 'SQLiteLockFile',
|
|
'LockBase', 'locked']
|
|
|
|
class Error(Exception):
|
|
"""
|
|
Base class for other exceptions.
|
|
|
|
>>> try:
|
|
... raise Error
|
|
... except Exception:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class LockError(Error):
|
|
"""
|
|
Base class for error arising from attempts to acquire the lock.
|
|
|
|
>>> try:
|
|
... raise LockError
|
|
... except Error:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class LockTimeout(LockError):
|
|
"""Raised when lock creation fails within a user-defined period of time.
|
|
|
|
>>> try:
|
|
... raise LockTimeout
|
|
... except LockError:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class AlreadyLocked(LockError):
|
|
"""Some other thread/process is locking the file.
|
|
|
|
>>> try:
|
|
... raise AlreadyLocked
|
|
... except LockError:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class LockFailed(LockError):
|
|
"""Lock file creation failed for some other reason.
|
|
|
|
>>> try:
|
|
... raise LockFailed
|
|
... except LockError:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class UnlockError(Error):
|
|
"""
|
|
Base class for errors arising from attempts to release the lock.
|
|
|
|
>>> try:
|
|
... raise UnlockError
|
|
... except Error:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class NotLocked(UnlockError):
|
|
"""Raised when an attempt is made to unlock an unlocked file.
|
|
|
|
>>> try:
|
|
... raise NotLocked
|
|
... except UnlockError:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class NotMyLock(UnlockError):
|
|
"""Raised when an attempt is made to unlock a file someone else locked.
|
|
|
|
>>> try:
|
|
... raise NotMyLock
|
|
... except UnlockError:
|
|
... pass
|
|
"""
|
|
pass
|
|
|
|
class LockBase:
|
|
"""Base class for platform-specific lock classes."""
|
|
def __init__(self, path, threaded=True, timeout=None):
|
|
"""
|
|
>>> lock = LockBase('somefile')
|
|
>>> lock = LockBase('somefile', threaded=False)
|
|
"""
|
|
self.path = path
|
|
self.lock_file = os.path.abspath(path) + ".lock"
|
|
self.hostname = socket.gethostname()
|
|
self.pid = os.getpid()
|
|
if threaded:
|
|
t = threading.current_thread()
|
|
# Thread objects in Python 2.4 and earlier do not have ident
|
|
# attrs. Worm around that.
|
|
ident = getattr(t, "ident", hash(t))
|
|
self.tname = "-%x" % (ident & 0xffffffff)
|
|
else:
|
|
self.tname = ""
|
|
dirname = os.path.dirname(self.lock_file)
|
|
self.unique_name = os.path.join(dirname,
|
|
"%s%s.%s" % (self.hostname,
|
|
self.tname,
|
|
self.pid))
|
|
self.timeout = timeout
|
|
|
|
def acquire(self, timeout=None):
|
|
"""
|
|
Acquire the lock.
|
|
|
|
* If timeout is omitted (or None), wait forever trying to lock the
|
|
file.
|
|
|
|
* If timeout > 0, try to acquire the lock for that many seconds. If
|
|
the lock period expires and the file is still locked, raise
|
|
LockTimeout.
|
|
|
|
* If timeout <= 0, raise AlreadyLocked immediately if the file is
|
|
already locked.
|
|
"""
|
|
raise NotImplemented("implement in subclass")
|
|
|
|
def release(self):
|
|
"""
|
|
Release the lock.
|
|
|
|
If the file is not locked, raise NotLocked.
|
|
"""
|
|
raise NotImplemented("implement in subclass")
|
|
|
|
def is_locked(self):
|
|
"""
|
|
Tell whether or not the file is locked.
|
|
"""
|
|
raise NotImplemented("implement in subclass")
|
|
|
|
def i_am_locking(self):
|
|
"""
|
|
Return True if this object is locking the file.
|
|
"""
|
|
raise NotImplemented("implement in subclass")
|
|
|
|
def break_lock(self):
|
|
"""
|
|
Remove a lock. Useful if a locking thread failed to unlock.
|
|
"""
|
|
raise NotImplemented("implement in subclass")
|
|
|
|
def __enter__(self):
|
|
"""
|
|
Context manager support.
|
|
"""
|
|
self.acquire()
|
|
return self
|
|
|
|
def __exit__(self, *_exc):
|
|
"""
|
|
Context manager support.
|
|
"""
|
|
self.release()
|
|
|
|
def __repr__(self):
|
|
return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
|
|
self.path)
|
|
|
|
def _fl_helper(cls, mod, *args, **kwds):
|
|
warnings.warn("Import from %s module instead of lockfile package" % mod,
|
|
DeprecationWarning, stacklevel=2)
|
|
# This is a bit funky, but it's only for awhile. The way the unit tests
|
|
# are constructed this function winds up as an unbound method, so it
|
|
# actually takes three args, not two. We want to toss out self.
|
|
if not isinstance(args[0], str):
|
|
# We are testing, avoid the first arg
|
|
args = args[1:]
|
|
if len(args) == 1 and not kwds:
|
|
kwds["threaded"] = True
|
|
return cls(*args, **kwds)
|
|
|
|
def LinkFileLock(*args, **kwds):
|
|
"""Factory function provided for backwards compatibility.
|
|
|
|
Do not use in new code. Instead, import LinkLockFile from the
|
|
lockfile.linklockfile module.
|
|
"""
|
|
from . import linklockfile
|
|
return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
|
|
*args, **kwds)
|
|
|
|
def MkdirFileLock(*args, **kwds):
|
|
"""Factory function provided for backwards compatibility.
|
|
|
|
Do not use in new code. Instead, import MkdirLockFile from the
|
|
lockfile.mkdirlockfile module.
|
|
"""
|
|
from . import mkdirlockfile
|
|
return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
|
|
*args, **kwds)
|
|
|
|
def SQLiteFileLock(*args, **kwds):
|
|
"""Factory function provided for backwards compatibility.
|
|
|
|
Do not use in new code. Instead, import SQLiteLockFile from the
|
|
lockfile.mkdirlockfile module.
|
|
"""
|
|
from . import sqlitelockfile
|
|
return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
|
|
*args, **kwds)
|
|
|
|
def locked(path, timeout=None):
|
|
"""Decorator which enables locks for decorated function.
|
|
|
|
Arguments:
|
|
- path: path for lockfile.
|
|
- timeout (optional): Timeout for acquiring lock.
|
|
|
|
Usage:
|
|
@locked('/var/run/myname', timeout=0)
|
|
def myname(...):
|
|
...
|
|
"""
|
|
def decor(func):
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
lock = FileLock(path, timeout=timeout)
|
|
lock.acquire()
|
|
try:
|
|
return func(*args, **kwargs)
|
|
finally:
|
|
lock.release()
|
|
return wrapper
|
|
return decor
|
|
|
|
if hasattr(os, "link"):
|
|
from . import linklockfile as _llf
|
|
LockFile = _llf.LinkLockFile
|
|
else:
|
|
from . import mkdirlockfile as _mlf
|
|
LockFile = _mlf.MkdirLockFile
|
|
|
|
FileLock = LockFile
|
|
|