From f4f2cf3ca5cddd8ee6e8bc7e1b8650f4bc24356d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jan 2026 09:24:29 +0530 Subject: [PATCH] Fix re-deleting the same book format not updating mtime of the formats fir in caltrash --- src/calibre/db/backend.py | 4 ++-- src/calibre/db/utils.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 3da1a65754..884d86fe08 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -49,6 +49,7 @@ from calibre.db.tables import ( SizeTable, UUIDTable, ) +from calibre.db.utils import atomic_write from calibre.ebooks.metadata import author_to_author_sort, title_sort from calibre.library.field_metadata import FieldMetadata from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile @@ -2284,8 +2285,7 @@ class DB: for path in format_abspaths: ext = path.rpartition('.')[-1].lower() fmap[path] = os.path.join(dest, ext) - with open(os.path.join(dest, 'metadata.json'), 'wb') as f: - f.write(json.dumps(metadata).encode('utf-8')) + atomic_write(os.path.join(dest, 'metadata.json'), json.dumps(metadata).encode('utf-8')) copy_files(fmap, delete_source=True) def get_metadata_for_trash_book(self, book_id, read_annotations=True): diff --git a/src/calibre/db/utils.py b/src/calibre/db/utils.py index d6a49f0110..45d9ab637b 100644 --- a/src/calibre/db/utils.py +++ b/src/calibre/db/utils.py @@ -9,6 +9,7 @@ import os import re import shutil import sys +import tempfile from collections import OrderedDict, namedtuple from contextlib import suppress from locale import localeconv @@ -16,10 +17,20 @@ from threading import Lock from calibre import as_unicode, prints from calibre.constants import cache_dir, get_windows_number_formats, iswindows, preferred_encoding +from calibre.utils.filenames import make_long_path_useable from calibre.utils.icu import lower as icu_lower from calibre.utils.localization import canonicalize_lang, ngettext +def atomic_write(path: str, data: str | bytes) -> None: + mode = 'w' if isinstance(data, str) else 'wb' + dpath = make_long_path_useable(os.path.dirname(path)) + os.makedirs(dpath, exist_ok=True) + with tempfile.NamedTemporaryFile(mode, delete=False, dir=dpath) as f: + f.write(data) + os.replace(make_long_path_useable(f.name), make_long_path_useable(path)) + + def force_to_bool(val): if isinstance(val, (bytes, str)): if isinstance(val, bytes):