fix(thumbnails): keep the new preview, probe encoders only when gone, sweep reused PIDs

- Convert never removes a path that is the preview it just wrote. It
  compares the device and inode, or the resolved and normcased paths when
  the disk has no inode numbers. This covers x_thumb.JPG on a
  case-insensitive disk.
- A JPEG XL encode failure re-runs encoder detection only when the
  encoder's binary or module is gone. A "no encoder" result is checked
  again on the settings read, on a jxl save, and after 60 s, so one slow
  probe no longer turns JPEG XL off until a restart.
- The startup sweep now also removes temp files that carry this process's
  PID but predate its start. Containers reuse PIDs.
This commit is contained in:
Vito Sansevero
2026-10-24 15:35:43 -07:00
parent 1e9ed87454
commit 7c82fdb180
6 changed files with 174 additions and 25 deletions
+17
View File
@@ -110,6 +110,22 @@ def _group_targets(targets):
return ordered
def _same_file(a, b):
"""Whether *a* and *b* name one file (re-review N1: on a case-insensitive
disk ``x_thumb.JPG`` is the ``x_thumb.jpg`` just written). Without inode
numbers (st_ino 0 on some SMB/FAT mounts) the resolved, normcased paths
are compared."""
try:
first, second = os.stat(a), os.stat(b)
except OSError:
return False
if first.st_ino and second.st_ino:
return (first.st_dev, first.st_ino) == (second.st_dev, second.st_ino)
return os.path.normcase(os.path.realpath(a)) == os.path.normcase(
os.path.realpath(b)
)
def _format_changed(settings):
"""Whether previews are written in another format than *settings* now."""
return writer_settings().extension != settings.extension
@@ -297,6 +313,7 @@ class ThumbnailConvertMixin:
return {"action": "error", "error": SOURCE_GONE}
if _format_changed(settings):
return {"action": "stopped"}
old = [(path, size) for path, size in old if not _same_file(path, dst)]
removed, kept = self._remove_old_previews(old)
if kept:
return {"action": "error", "error": OLD_NOT_REMOVED}