mirror of
https://github.com/gryf/ebook-converter.git
synced 2025-12-28 04:02:27 +01:00
Here is the first batch of modules, which are needed for converting several formats to LRF. Some of the logic has been change, more cleanups will follow.
209 lines
6.5 KiB
Python
209 lines
6.5 KiB
Python
# License: GPLv3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
|
|
|
import atexit
|
|
import errno
|
|
import os
|
|
import stat
|
|
import tempfile
|
|
import time
|
|
from functools import partial
|
|
|
|
from ebook_converter.constants import (
|
|
__appname__, fcntl, filesystem_encoding, islinux, isosx, iswindows, plugins, ispy3
|
|
)
|
|
from ebook_converter.utils.monotonic import monotonic
|
|
|
|
# speedup = plugins['speedup'][0]
|
|
if iswindows:
|
|
import msvcrt, win32file, pywintypes, winerror, win32api, win32event
|
|
from ebook_converter.constants import get_windows_username
|
|
excl_file_mode = stat.S_IREAD | stat.S_IWRITE
|
|
else:
|
|
excl_file_mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
|
|
|
|
|
def unix_open(path):
|
|
flags = os.O_RDWR | os.O_CREAT
|
|
has_cloexec = False
|
|
#if hasattr(speedup, 'O_CLOEXEC'):
|
|
# try:
|
|
# fd = os.open(path, flags | speedup.O_CLOEXEC, excl_file_mode)
|
|
# has_cloexec = True
|
|
# except EnvironmentError as err:
|
|
# # Kernel may not support O_CLOEXEC
|
|
# if err.errno != errno.EINVAL:
|
|
# raise
|
|
|
|
if not has_cloexec:
|
|
fd = os.open(path, flags, excl_file_mode)
|
|
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
|
|
return os.fdopen(fd, 'r+b')
|
|
|
|
|
|
def unix_retry(err):
|
|
return err.errno in (errno.EACCES, errno.EAGAIN, errno.ENOLCK, errno.EINTR)
|
|
|
|
|
|
def windows_open(path):
|
|
if isinstance(path, bytes):
|
|
path = path.decode('mbcs')
|
|
try:
|
|
h = win32file.CreateFileW(
|
|
path,
|
|
win32file.GENERIC_READ |
|
|
win32file.GENERIC_WRITE, # Open for reading and writing
|
|
0, # Open exclusive
|
|
None, # No security attributes, ensures handle is not inherited by children
|
|
win32file.OPEN_ALWAYS, # If file does not exist, create it
|
|
win32file.FILE_ATTRIBUTE_NORMAL, # Normal attributes
|
|
None, # No template file
|
|
)
|
|
except pywintypes.error as err:
|
|
raise WindowsError(err[0], err[2], path)
|
|
fd = msvcrt.open_osfhandle(h.Detach(), 0)
|
|
return os.fdopen(fd, 'r+b')
|
|
|
|
|
|
def windows_retry(err):
|
|
return err.winerror in (
|
|
winerror.ERROR_SHARING_VIOLATION, winerror.ERROR_LOCK_VIOLATION
|
|
)
|
|
|
|
|
|
def retry_for_a_time(timeout, sleep_time, func, error_retry, *args):
|
|
limit = monotonic() + timeout
|
|
while True:
|
|
try:
|
|
return func(*args)
|
|
except EnvironmentError as err:
|
|
if not error_retry(err) or monotonic() > limit:
|
|
raise
|
|
time.sleep(sleep_time)
|
|
|
|
|
|
def _lock_file(path, timeout=15, sleep_time=0.2):
|
|
if iswindows:
|
|
return retry_for_a_time(
|
|
timeout, sleep_time, windows_open, windows_retry, path
|
|
)
|
|
f = unix_open(path)
|
|
retry_for_a_time(
|
|
timeout, sleep_time, fcntl.flock, unix_retry,
|
|
f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB
|
|
)
|
|
return f
|
|
|
|
|
|
def lock_file(path, timeout=15, sleep_time=0.2):
|
|
from filelock import FileLock
|
|
lock = FileLock(path + '.lock', timeout=timeout)
|
|
with lock:
|
|
return unix_open(path)
|
|
|
|
|
|
class ExclusiveFile(object):
|
|
|
|
def __init__(self, path, timeout=15, sleep_time=0.2):
|
|
if iswindows and isinstance(path, bytes):
|
|
path = path.decode(filesystem_encoding)
|
|
self.path = path
|
|
self.timeout = timeout
|
|
self.sleep_time = sleep_time
|
|
|
|
def __enter__(self):
|
|
self.file = lock_file(self.path, self.timeout, self.sleep_time)
|
|
return self.file
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
self.file.close()
|
|
|
|
|
|
def _clean_lock_file(file_obj):
|
|
try:
|
|
os.remove(file_obj.name)
|
|
except EnvironmentError:
|
|
pass
|
|
try:
|
|
file_obj.close()
|
|
except EnvironmentError:
|
|
pass
|
|
|
|
|
|
if iswindows:
|
|
|
|
def create_single_instance_mutex(name, per_user=True):
|
|
mutexname = '{}-singleinstance-{}-{}'.format(
|
|
__appname__, (get_windows_username() if per_user else ''), name
|
|
)
|
|
mutex = win32event.CreateMutex(None, False, mutexname)
|
|
if not mutex:
|
|
return
|
|
err = win32api.GetLastError()
|
|
if err == winerror.ERROR_ALREADY_EXISTS:
|
|
# Close this handle other wise this handle will prevent the mutex
|
|
# from being deleted when the process that created it exits.
|
|
win32api.CloseHandle(mutex)
|
|
return
|
|
return partial(win32api.CloseHandle, mutex)
|
|
|
|
elif islinux:
|
|
|
|
def create_single_instance_mutex(name, per_user=True):
|
|
import socket
|
|
from ebook_converter.utils.ipc import eintr_retry_call
|
|
name = '%s-singleinstance-%s-%s' % (
|
|
__appname__, (os.geteuid() if per_user else ''), name
|
|
)
|
|
name = name
|
|
address = '\0' + name.replace(' ', '_')
|
|
if not ispy3:
|
|
address = address.encode('utf-8')
|
|
sock = socket.socket(family=socket.AF_UNIX)
|
|
try:
|
|
eintr_retry_call(sock.bind, address)
|
|
except socket.error as err:
|
|
if getattr(err, 'errno', None) == errno.EADDRINUSE:
|
|
return
|
|
raise
|
|
fd = sock.fileno()
|
|
old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
|
fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
|
|
return sock.close
|
|
|
|
else:
|
|
|
|
def singleinstance_path(name, per_user=True):
|
|
name = '%s-singleinstance-%s-%s.lock' % (
|
|
__appname__, (os.geteuid() if per_user else ''), name
|
|
)
|
|
home = os.path.expanduser('~')
|
|
locs = ['/var/lock', home, tempfile.gettempdir()]
|
|
if isosx:
|
|
locs.insert(0, '/Library/Caches')
|
|
for loc in locs:
|
|
if os.access(loc, os.W_OK | os.R_OK | os.X_OK):
|
|
return os.path.join(loc, ('.' if loc is home else '') + name)
|
|
raise EnvironmentError(
|
|
'Failed to find a suitable filesystem location for the lock file'
|
|
)
|
|
|
|
def create_single_instance_mutex(name, per_user=True):
|
|
from ebook_converter.utils.ipc import eintr_retry_call
|
|
path = singleinstance_path(name, per_user)
|
|
f = lopen(path, 'w')
|
|
try:
|
|
eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
return partial(_clean_lock_file, f)
|
|
except EnvironmentError as err:
|
|
if err.errno not in (errno.EAGAIN, errno.EACCES):
|
|
raise
|
|
|
|
|
|
def singleinstance(name):
|
|
' Ensure that only a single process holding exists with the specified mutex key '
|
|
release_mutex = create_single_instance_mutex(name)
|
|
if release_mutex is None:
|
|
return False
|
|
atexit.register(release_mutex)
|
|
return True
|