From 976f3a2fdd4dc29f55fd43983cbe26f5b87064ed Mon Sep 17 00:00:00 2001 From: andreasgriffin Date: Fri, 9 Jan 2026 11:53:45 +0100 Subject: [PATCH] squash --- bitcoin_safe/pdf_labels.py | 22 +++++---------- bitcoin_safe/pdf_statement.py | 8 ++---- bitcoin_safe/pdfrecovery.py | 41 ++++++++++++++++++++++++---- tests/gui/qt/test_setup_wallet.py | 12 ++++---- tests/gui/qt/test_wallet_features.py | 12 ++++---- 5 files changed, 59 insertions(+), 36 deletions(-) diff --git a/bitcoin_safe/pdf_labels.py b/bitcoin_safe/pdf_labels.py index 0a9cabd9..2065a24f 100644 --- a/bitcoin_safe/pdf_labels.py +++ b/bitcoin_safe/pdf_labels.py @@ -28,9 +28,6 @@ from __future__ import annotations -import logging -import os -from pathlib import Path from typing import Any from reportlab.lib import colors @@ -40,9 +37,7 @@ from reportlab.platypus import KeepInFrame, Paragraph, Spacer, Table, TableStyle from bitcoin_safe.i18n import translate -from bitcoin_safe.pdfrecovery import BasePDF, white_space - -logger = logging.getLogger(__name__) +from bitcoin_safe.pdfrecovery import BasePDF, white_space, write_and_open_temp_pdf class PDFLabels(BasePDF): @@ -166,12 +161,9 @@ def make_and_open_labels_pdf(wallet_id: str, label_pairs: list[tuple[str, str]], pdf_labels = PDFLabels(lang_code=lang_code) pdf_labels.add_labels(wallet_id=wallet_id, label_pairs=label_pairs) - filename = ( - translate("pdf", "Hardware signer labels for {id}", no_translate=pdf_labels.no_translate).format( - id=wallet_id - ) - + ".pdf" - ) - temp_file = os.path.join(Path.home(), filename) - pdf_labels.save_pdf(temp_file) - pdf_labels.open_pdf(temp_file) + filename = translate( + "pdf", + "Hardware signer labels for {id}", + no_translate=pdf_labels.no_translate, + ).format(id=wallet_id) + write_and_open_temp_pdf(pdf_labels, filename) diff --git a/bitcoin_safe/pdf_statement.py b/bitcoin_safe/pdf_statement.py index aef9e1d1..b8ebeaa6 100644 --- a/bitcoin_safe/pdf_statement.py +++ b/bitcoin_safe/pdf_statement.py @@ -30,8 +30,6 @@ import datetime import logging -import os -from pathlib import Path from typing import Any import bdkpython as bdk @@ -46,7 +44,7 @@ from reportlab.platypus import PageBreak, Paragraph, Table, TableStyle from bitcoin_safe.i18n import translate -from bitcoin_safe.pdfrecovery import BasePDF, pilimage_to_reportlab, white_space +from bitcoin_safe.pdfrecovery import BasePDF, pilimage_to_reportlab, white_space, write_and_open_temp_pdf from .wallet import Wallet @@ -359,6 +357,4 @@ def make_and_open_pdf_statement(wallet: Wallet, lang_code: str, label_sync_nsec: label_sync_nsec=label_sync_nsec, ) - temp_file = os.path.join(Path.home(), f"{file_title}.pdf") - pdf_statement.save_pdf(temp_file) - pdf_statement.open_pdf(temp_file) + write_and_open_temp_pdf(pdf_statement, file_title) diff --git a/bitcoin_safe/pdfrecovery.py b/bitcoin_safe/pdfrecovery.py index 488dce2b..39e7455c 100644 --- a/bitcoin_safe/pdfrecovery.py +++ b/bitcoin_safe/pdfrecovery.py @@ -28,14 +28,17 @@ from __future__ import annotations +import atexit import io import logging import os +import tempfile from dataclasses import dataclass from enum import Enum from pathlib import Path from typing import Any +import platformdirs from bitcoin_qr_tools.qr_generator import QRGenerator from bitcoin_usb.address_types import DescriptorInfo from PIL.Image import Image as PilImage @@ -64,11 +67,42 @@ logger = logging.getLogger(__name__) +_TEMP_PDFS: set[Path] = set() + + +def _cleanup_temp_pdfs() -> None: + for path in list(_TEMP_PDFS): + try: + path.unlink() + except FileNotFoundError: + pass + except PermissionError: + logger.warning("Could not remove temporary PDF at %s", path) + + +atexit.register(_cleanup_temp_pdfs) + TEXT_24_WORDS = translate("pdf", "12 or 24") DEFAULT_MARGIN = 36 +def _safe_filename_prefix(filename: str) -> str: + return "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in filename) + + +def write_and_open_temp_pdf(pdf: BasePDF, filename: str) -> None: + """Write a PDF to the user cache directory and open it with the OS.""" + safe_prefix = _safe_filename_prefix(filename) + cache_dir = Path(platformdirs.user_cache_dir("bitcoin_safe")) + cache_dir.mkdir(parents=True, exist_ok=True) + temp_fd, temp_file = tempfile.mkstemp(prefix=f"{safe_prefix}-", suffix=".pdf", dir=cache_dir) + os.close(temp_fd) + pdf.save_pdf(temp_file) + pdf.open_pdf(temp_file) + _TEMP_PDFS.add(Path(temp_file)) + + def pilimage_to_reportlab(pilimage: PilImage, width=200, height=200) -> Image: """Pilimage to reportlab.""" buffer = io.BytesIO() @@ -602,8 +636,5 @@ def make_and_open_pdf(wallet: Wallet, lang_code: str) -> None: keystore_label=keystore.label, ) pdf_recovery.add_page_break() - temp_file = os.path.join( - Path.home(), translate("pdf", "Seed backup of {id}").format(id=wallet.id) + ".pdf" - ) - pdf_recovery.save_pdf(temp_file) - pdf_recovery.open_pdf(temp_file) + filename = translate("pdf", "Seed backup of {id}").format(id=wallet.id) + write_and_open_temp_pdf(pdf_recovery, filename) diff --git a/tests/gui/qt/test_setup_wallet.py b/tests/gui/qt/test_setup_wallet.py index fa220ee2..41ab8af9 100644 --- a/tests/gui/qt/test_setup_wallet.py +++ b/tests/gui/qt/test_setup_wallet.py @@ -30,12 +30,12 @@ import inspect import logging -import os from datetime import datetime from pathlib import Path from unittest.mock import patch import bdkpython as bdk +import platformdirs import pytest from bitcoin_safe_lib.gui.qt.satoshis import Satoshis from bitcoin_safe_lib.util import insert_invisible_spaces_for_wordwrap @@ -321,10 +321,12 @@ def page_backup() -> None: step.custom_yes_button.click() mock_open.assert_called_once() - temp_file = os.path.join(Path.home(), f"Seed backup of {wallet_name}.pdf") - assert Path(temp_file).exists() - # remove the file again - Path(temp_file).unlink() + cache_dir = Path(platformdirs.user_cache_dir("bitcoin_safe")) + prefix = f"Seed backup of {wallet_name}".replace(" ", "_") + temp_files = list(cache_dir.glob(f"{prefix}-*.pdf")) + assert temp_files + for temp_file in temp_files: + temp_file.unlink() page_backup() diff --git a/tests/gui/qt/test_wallet_features.py b/tests/gui/qt/test_wallet_features.py index 86b9850d..ec0ea3a1 100644 --- a/tests/gui/qt/test_wallet_features.py +++ b/tests/gui/qt/test_wallet_features.py @@ -30,7 +30,6 @@ import inspect import logging -import os import platform import tempfile from datetime import datetime @@ -38,6 +37,7 @@ from unittest.mock import patch import bdkpython as bdk +import platformdirs import pytest from bitcoin_qr_tools.gui.bitcoin_video_widget import BitcoinVideoWidget from bitcoin_usb.address_types import AddressTypes @@ -293,10 +293,12 @@ def menu_action_export_pdf() -> None: mock_open.assert_called_once() - temp_file = os.path.join(Path.home(), f"Seed backup of {wallet_name}.pdf") - assert Path(temp_file).exists() - # remove the file again - Path(temp_file).unlink() + cache_dir = Path(platformdirs.user_cache_dir("bitcoin_safe")) + prefix = f"Seed backup of {wallet_name}".replace(" ", "_") + temp_files = list(cache_dir.glob(f"{prefix}-*.pdf")) + assert temp_files + for temp_file in temp_files: + temp_file.unlink() menu_action_export_pdf()