1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> 4 5 6import atexit 7import errno 8import os 9import tempfile 10import time 11 12from calibre.constants import cache_dir, iswindows 13from calibre.ptempfile import remove_dir 14from calibre.utils.monotonic import monotonic 15 16TDIR_LOCK = 'tdir-lock' 17 18if iswindows: 19 from calibre.utils.lock import windows_open 20 21 def lock_tdir(path): 22 return windows_open(os.path.join(path, TDIR_LOCK)) 23 24 def unlock_file(fobj): 25 fobj.close() 26 27 def remove_tdir(path, lock_file): 28 lock_file.close() 29 remove_dir(path) 30 31 def is_tdir_locked(path): 32 try: 33 with windows_open(os.path.join(path, TDIR_LOCK)): 34 pass 35 except OSError: 36 return True 37 return False 38else: 39 import fcntl 40 from calibre.utils.ipc import eintr_retry_call 41 42 def lock_tdir(path): 43 lf = os.path.join(path, TDIR_LOCK) 44 f = lopen(lf, 'w') 45 eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) 46 return f 47 48 def unlock_file(fobj): 49 from calibre.utils.ipc import eintr_retry_call 50 eintr_retry_call(fcntl.lockf, fobj.fileno(), fcntl.LOCK_UN) 51 fobj.close() 52 53 def remove_tdir(path, lock_file): 54 lock_file.close() 55 remove_dir(path) 56 57 def is_tdir_locked(path): 58 lf = os.path.join(path, TDIR_LOCK) 59 f = lopen(lf, 'w') 60 try: 61 eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) 62 eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_UN) 63 return False 64 except OSError: 65 return True 66 finally: 67 f.close() 68 69 70def tdirs_in(b): 71 try: 72 tdirs = os.listdir(b) 73 except OSError as e: 74 if e.errno != errno.ENOENT: 75 raise 76 tdirs = () 77 for x in tdirs: 78 x = os.path.join(b, x) 79 if os.path.isdir(x): 80 yield x 81 82 83def clean_tdirs_in(b): 84 # Remove any stale tdirs left by previous program crashes 85 for q in tdirs_in(b): 86 if not is_tdir_locked(q): 87 remove_dir(q) 88 89 90def retry_lock_tdir(path, timeout=30, sleep=0.1): 91 st = monotonic() 92 while True: 93 try: 94 return lock_tdir(path) 95 except Exception: 96 if monotonic() - st > timeout: 97 raise 98 time.sleep(sleep) 99 100 101def tdir_in_cache(base): 102 ''' Create a temp dir inside cache_dir/base. The created dir is robust 103 against application crashes. i.e. it will be cleaned up the next time the 104 application starts, even if it was left behind by a previous crash. ''' 105 b = os.path.join(os.path.realpath(cache_dir()), base) 106 try: 107 os.makedirs(b) 108 except OSError as e: 109 if e.errno != errno.EEXIST: 110 raise 111 global_lock = retry_lock_tdir(b) 112 try: 113 if b not in tdir_in_cache.scanned: 114 tdir_in_cache.scanned.add(b) 115 try: 116 clean_tdirs_in(b) 117 except Exception: 118 import traceback 119 traceback.print_exc() 120 tdir = tempfile.mkdtemp(dir=b) 121 lock_data = lock_tdir(tdir) 122 atexit.register(remove_tdir, tdir, lock_data) 123 tdir = os.path.join(tdir, 'a') 124 os.mkdir(tdir) 125 return tdir 126 finally: 127 unlock_file(global_lock) 128 129 130tdir_in_cache.scanned = set() 131