From 6bc7f696d96d47a42ce8afe3b7dc211f7782de04 Mon Sep 17 00:00:00 2001 From: Fikri Date: Wed, 15 Apr 2026 13:00:23 +0200 Subject: [PATCH] fix: Improve resource handling and error handling - Use delete=False for temp files and proper cleanup on exception - Add null checks for profile lookups using get_or_none() - Improve error handling in archive template saving - Use context manager for file reads - Fix f-string formatting in scheduler debug message --- src/vorta/borg/create.py | 15 ++++++++++----- src/vorta/scheduler.py | 8 ++++++-- src/vorta/views/archive_tab.py | 3 ++- src/vorta/views/base_tab.py | 8 +++++++- src/vorta/views/main_window.py | 5 ++++- src/vorta/views/repo_tab.py | 3 ++- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/vorta/borg/create.py b/src/vorta/borg/create.py index 7983e0cff..d43aafc79 100644 --- a/src/vorta/borg/create.py +++ b/src/vorta/borg/create.py @@ -188,11 +188,16 @@ def prepare(cls, profile): exclude_dirs.append(expanded_directory) if exclude_dirs: - pattern_file = tempfile.NamedTemporaryFile('w', delete=True) - pattern_file.write('\n'.join(exclude_dirs)) - pattern_file.flush() - cmd.extend(['--exclude-from', pattern_file.name]) - ret['cleanup_files'].append(pattern_file) + pattern_file = tempfile.NamedTemporaryFile('w', delete=False) + try: + pattern_file.write('\n'.join(exclude_dirs)) + pattern_file.flush() + cmd.extend(['--exclude-from', pattern_file.name]) + ret['cleanup_files'].append(pattern_file) + except Exception: + pattern_file.close() + os.unlink(pattern_file.name) + raise # Currently not in use, but may be added back to the UI later. # if profile.exclude_if_present is not None: diff --git a/src/vorta/scheduler.py b/src/vorta/scheduler.py index e5c69ca63..3b3a65def 100644 --- a/src/vorta/scheduler.py +++ b/src/vorta/scheduler.py @@ -372,7 +372,7 @@ def set_timer_for_profile(self, profile_id: int) -> None: else: # int to big to pass it to qt which expects a c++ int # wait 15 min for regular reschedule - logger.debug(f"Couldn't schedule for {next_time} because " f"timer value {timer_ms} too large.") + logger.debug(f"Couldn't schedule for {next_time} because timer value {timer_ms} too large.") self.timers[profile_id] = { 'dt': next_time, @@ -499,7 +499,11 @@ def post_backup_tasks(self, profile_id: int) -> None: """ Pruning and checking after successful backup. """ - profile = BackupProfileModel.get(id=profile_id) + profile = BackupProfileModel.get_or_none(id=profile_id) + if profile is None: + logger.warning('Profile %s not found for post-backup tasks', profile_id) + return + notifier = VortaNotifications.pick() logger.info('Doing post-backup jobs for %s', profile.name) if profile.prune_on: diff --git a/src/vorta/views/archive_tab.py b/src/vorta/views/archive_tab.py index 24bc6fc7b..7e93bb26a 100644 --- a/src/vorta/views/archive_tab.py +++ b/src/vorta/views/archive_tab.py @@ -467,8 +467,9 @@ def save_archive_template(self, tpl, key): preview = self.tr('Preview: %s') % format_archive_name(profile, tpl) setattr(profile, key, tpl) profile.save() - except Exception: + except (KeyError, ValueError, AttributeError) as e: preview = self.tr('Error in archive name template.') + logger.warning('Invalid archive name template: %s', e) if key == 'new_archive_name': self.archiveNamePreview.setText(preview) diff --git a/src/vorta/views/base_tab.py b/src/vorta/views/base_tab.py index fb7dfe7de..600064ceb 100644 --- a/src/vorta/views/base_tab.py +++ b/src/vorta/views/base_tab.py @@ -1,11 +1,14 @@ from __future__ import annotations +import logging from typing import Any, Callable from PyQt6.QtWidgets import QApplication from vorta.store.models import BackupProfileModel, RepoModel +logger = logging.getLogger(__name__) + ProfileProvider = Callable[[], BackupProfileModel | None] @@ -28,7 +31,10 @@ def _default_profile_provider(self) -> BackupProfileModel | None: current_profile = getattr(self.window(), 'current_profile', None) if current_profile is None: return None - return BackupProfileModel.get(id=current_profile.id) + profile = BackupProfileModel.get_or_none(id=current_profile.id) + if profile is None: + logger.warning(f"Profile with id {current_profile.id} no longer exists") + return profile def current_profile(self) -> BackupProfileModel | None: return self._profile_provider() diff --git a/src/vorta/views/main_window.py b/src/vorta/views/main_window.py index 9d5588362..f945bfdda 100644 --- a/src/vorta/views/main_window.py +++ b/src/vorta/views/main_window.py @@ -138,7 +138,10 @@ def on_close_window(self): self.close() def get_current_profile(self): - return BackupProfileModel.get(id=self.current_profile.id) + profile = BackupProfileModel.get_or_none(id=self.current_profile.id) + if profile is None: + logging.warning(f"Profile with id {self.current_profile.id} no longer exists") + return profile def set_icons(self): self.profileAddButton.setIcon(get_colored_icon('plus')) diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index 0a661857e..47da1a4aa 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -247,7 +247,8 @@ def ssh_copy_to_clipboard_action(self): ssh_key_filename = self.sshComboBox.itemData(index) ssh_key_path = os.path.expanduser(f'~/.ssh/{ssh_key_filename}.pub') if os.path.isfile(ssh_key_path): - pub_key = open(ssh_key_path).read().strip() + with open(ssh_key_path) as f: + pub_key = f.read().strip() clipboard = QApplication.clipboard() clipboard.setText(pub_key)