Ensure calibre temp files are deleted even on crash

Also, only create the calibre temp file folder on demand.
This commit is contained in:
Kovid Goyal 2025-03-26 15:33:40 +05:30
parent e90b79cf17
commit e4da8e2172
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 33 additions and 62 deletions

View File

@ -4,54 +4,15 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Provides platform independent temporary files that persist even after Provides platform independent temporary files that persist even after
being closed. being closed.
''' '''
import atexit
import os import os
import tempfile import tempfile
from calibre.constants import __appname__, __version__, filesystem_encoding, get_windows_temp_path, ismacos, iswindows from calibre.constants import __appname__, __version__, filesystem_encoding, get_windows_temp_path, ismacos, iswindows
from calibre.utils.safe_atexit import remove_dir, remove_file_atexit, remove_folder_atexit, unlink
def cleanup(path):
try:
import os as oss
if oss.path.exists(path):
oss.remove(path)
except:
pass
_base_dir = None _base_dir = None
def remove_dir(x):
try:
import shutil
shutil.rmtree(x, ignore_errors=True)
except:
pass
def determined_remove_dir(x):
for i in range(10):
try:
import shutil
shutil.rmtree(x)
return
except:
import os # noqa
if os.path.exists(x):
# In case some other program has one of the temp files open.
import time
time.sleep(0.1)
else:
return
try:
import shutil
shutil.rmtree(x, ignore_errors=True)
except:
pass
def app_prefix(prefix): def app_prefix(prefix):
if iswindows: if iswindows:
return f'{__appname__}_' return f'{__appname__}_'
@ -81,6 +42,9 @@ def osx_cache_dir():
return q return q
get_default_tempdir = tempfile.gettempdir
def base_dir(): def base_dir():
global _base_dir global _base_dir
if _base_dir is not None and not os.path.exists(_base_dir): if _base_dir is not None and not os.path.exists(_base_dir):
@ -119,23 +83,25 @@ def base_dir():
# https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/confstr.3.html # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/confstr.3.html
base = osx_cache_dir() base = osx_cache_dir()
_base_dir = tempfile.mkdtemp(prefix=prefix, dir=base) _base_dir = tempfile.mkdtemp(prefix=prefix, dir=base or get_default_tempdir())
atexit.register(determined_remove_dir if iswindows else remove_dir, _base_dir) remove_folder_atexit(_base_dir)
try:
tempfile.gettempdir()
except Exception:
# Widows temp vars set to a path not encodable in mbcs
# Use our temp dir
tempfile.tempdir = _base_dir
return _base_dir return _base_dir
def fix_tempfile_module():
# We want the tempfile module to use base_dir() as its tempdir, but we dont
# want to call base_dir() now as it will possibly create a tempdir, do that
# only on demand.
global get_default_tempdir
if tempfile._gettempdir is not base_dir:
get_default_tempdir = tempfile._gettempdir
tempfile._gettempdir = base_dir
def reset_base_dir(): def reset_base_dir():
global _base_dir global _base_dir
_base_dir = None _base_dir = None
base_dir()
def force_unicode(x): def force_unicode(x):
@ -173,7 +139,7 @@ class PersistentTemporaryFile:
self._file = os.fdopen(fd, mode) self._file = os.fdopen(fd, mode)
self._name = name self._name = name
self._fd = fd self._fd = fd
atexit.register(cleanup, name) remove_file_atexit(name)
def __getattr__(self, name): def __getattr__(self, name):
if name == 'name': if name == 'name':
@ -201,8 +167,7 @@ def PersistentTemporaryDirectory(suffix='', prefix='', dir=None):
if dir is None: if dir is None:
dir = base_dir() dir = base_dir()
tdir = _make_dir(suffix, prefix, dir) tdir = _make_dir(suffix, prefix, dir)
remove_folder_atexit(tdir)
atexit.register(remove_dir, tdir)
return tdir return tdir
@ -249,7 +214,7 @@ class TemporaryFile:
return name return name
def __exit__(self, *args): def __exit__(self, *args):
cleanup(self._name) unlink(self._name)
class SpooledTemporaryFile(tempfile.SpooledTemporaryFile): class SpooledTemporaryFile(tempfile.SpooledTemporaryFile):

View File

@ -72,15 +72,10 @@ def initialize_calibre():
if hasattr(initialize_calibre, 'initialized'): if hasattr(initialize_calibre, 'initialized'):
return return
initialize_calibre.initialized = True initialize_calibre.initialized = True
# Ensure that all temp files/dirs are created under a calibre tmp dir # Ensure that all temp files/dirs are created under a calibre tmp dir
from calibre.ptempfile import base_dir from calibre.ptempfile import fix_tempfile_module
try: fix_tempfile_module()
base_dir()
except OSError:
pass # Ignore this error during startup, so we can show a better error message to the user later.
#
# Ensure that the max number of open files is at least 1024 # Ensure that the max number of open files is at least 1024
if iswindows: if iswindows:
# See https://msdn.microsoft.com/en-us/library/6e3b887c.aspx # See https://msdn.microsoft.com/en-us/library/6e3b887c.aspx

View File

@ -104,7 +104,14 @@ def main():
traceback.print_exc() traceback.print_exc()
def main_for_test(do_forced_exit=False): def main_for_test(do_forced_exit=False, check_tdir=False):
if check_tdir:
import tempfile
from calibre.ptempfile import base_dir
print(tempfile.gettempdir())
print(base_dir())
return
tf = 'test-folder' tf = 'test-folder'
os.mkdir(tf) os.mkdir(tf)
open(os.path.join(tf, 'test-file'), 'w').close() open(os.path.join(tf, 'test-file'), 'w').close()
@ -146,5 +153,9 @@ def find_tests():
p = start_pipe_worker('from calibre.utils.safe_atexit import main_for_test; main_for_test(True)', cwd=tdir) p = start_pipe_worker('from calibre.utils.safe_atexit import main_for_test; main_for_test(True)', cwd=tdir)
p.wait(10) p.wait(10)
self.wait_for_empty(tdir) self.wait_for_empty(tdir)
p = start_pipe_worker('from calibre.utils.safe_atexit import main_for_test; main_for_test(check_tdir=True)')
tempfiledir, bdir = p.stdout.read().decode().splitlines()[:2]
p.wait(10)
self.assertEqual(bdir, tempfiledir)
return unittest.defaultTestLoader.loadTestsFromTestCase(TestSafeAtexit) return unittest.defaultTestLoader.loadTestsFromTestCase(TestSafeAtexit)