diff --git a/src/vorta/application.py b/src/vorta/application.py
index d87830d91..1bd8a50ef 100644
--- a/src/vorta/application.py
+++ b/src/vorta/application.py
@@ -21,6 +21,7 @@
from vorta.store.models import BackupProfileModel, SettingsModel
from vorta.tray_menu import TrayMenu
from vorta.utils import borg_compat, parse_args
+from vorta.views.exception_dialog import ExceptionDialog
from vorta.views.main_window import MainWindow
logger = logging.getLogger(__name__)
@@ -43,6 +44,7 @@ class VortaApp(QtSingleApplication):
backup_progress_event = QtCore.pyqtSignal(str)
check_failed_event = QtCore.pyqtSignal(dict)
profile_changed_event = QtCore.pyqtSignal()
+ error_signal = QtCore.pyqtSignal(str)
def __init__(self, args_raw, single_app=False):
super().__init__(str(APP_ID), args_raw)
@@ -86,6 +88,7 @@ def __init__(self, args_raw, single_app=False):
self.check_failed_event.connect(self.check_failed_response)
self.backup_log_event.connect(self.react_to_log)
self.aboutToQuit.connect(self.quit_app_action)
+ self.error_signal.connect(self.show_exception_dialog)
self.set_borg_details_action()
if sys.platform == 'darwin':
self.check_darwin_permissions()
@@ -157,6 +160,13 @@ def message_received_event_response(self, message):
else:
self.create_backups_cmdline(message)
+ def show_exception_dialog(self, error_msg):
+ exception_dialog = ExceptionDialog(error_msg)
+ exception_dialog.show()
+ exception_dialog.raise_()
+ exception_dialog.activateWindow()
+ exception_dialog.exec()
+
# No need to add this function to JobsManager because it doesn't require to lock a repo.
def set_borg_details_action(self):
params = BorgVersionJob.prepare()
@@ -289,7 +299,7 @@ def bootstrap_profile(self, bootstrap_file=None):
double_newline,
str(exception),
double_newline,
- self.tr('Consider removing or repairing this file to ' 'get rid of this message.'),
+ self.tr('Consider removing or repairing this file to get rid of this message.'),
),
)
return
diff --git a/src/vorta/borg/borg_job.py b/src/vorta/borg/borg_job.py
index 588ff2dd9..e3f215642 100644
--- a/src/vorta/borg/borg_job.py
+++ b/src/vorta/borg/borg_job.py
@@ -263,6 +263,7 @@ def read_async(fd):
return ''
stdout = []
+ error_msg = None
while True:
# Wait for new output
select.select([p.stdout, p.stderr], [], [], 0.1)
@@ -299,16 +300,16 @@ def read_async(fd):
self.app.backup_log_event.emit(f'[{self.params["profile_name"]}] {parsed["message"]}', {})
elif parsed['type'] == 'archive_progress' and not parsed.get('finished', False):
msg = (
- f"{translate('BorgJob','Files')}: {parsed['nfiles']}, "
- f"{translate('BorgJob','Original')}: {pretty_bytes(parsed['original_size'])}, "
+ f"{translate('BorgJob', 'Files')}: {parsed['nfiles']}, "
+ f"{translate('BorgJob', 'Original')}: {pretty_bytes(parsed['original_size'])}, "
# f"{translate('BorgJob','Compressed')}: {pretty_bytes(parsed['compressed_size'])}, "
- f"{translate('BorgJob','Deduplicated')}: {pretty_bytes(parsed.get('deduplicated_size', 0))}" # noqa: E501
+ f"{translate('BorgJob', 'Deduplicated')}: {pretty_bytes(parsed.get('deduplicated_size', 0))}" # noqa: E501
)
self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {msg}")
except json.decoder.JSONDecodeError:
msg = line.strip()
if msg: # Log only if there is something to log.
- self.app.backup_log_event.emit(f'[{self.params["profile_name"]}] {msg}', {})
+ error_msg = stderr
logger.warning(msg)
if p.poll() is not None:
@@ -316,6 +317,9 @@ def read_async(fd):
stdout.append(read_async(p.stdout))
break
+ if error_msg and self.process.returncode != 0:
+ self.app.error_signal.emit(error_msg)
+
result = {
'params': self.params,
'returncode': self.process.returncode,
diff --git a/src/vorta/borg/check.py b/src/vorta/borg/check.py
index e25443fdf..cc6ee28f2 100644
--- a/src/vorta/borg/check.py
+++ b/src/vorta/borg/check.py
@@ -26,9 +26,9 @@ def finished_event(self, result: Dict[str, Any]):
if result['returncode'] != 0:
self.app.backup_progress_event.emit(
f"[{self.params['profile_name']}] "
- + translate('RepoCheckJob', 'Repo check failed. See the logs for details.').format(
- config.LOG_DIR.as_uri()
- )
+ + translate(
+ 'RepoCheckJob', 'Repo check command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
)
self.app.check_failed_event.emit(result)
else:
diff --git a/src/vorta/borg/create.py b/src/vorta/borg/create.py
index 5cdb136ff..723152650 100644
--- a/src/vorta/borg/create.py
+++ b/src/vorta/borg/create.py
@@ -42,18 +42,6 @@ def process_result(self, result):
repo.total_unique_chunks = stats['total_unique_chunks']
repo.save()
- if result['returncode'] == 1:
- self.app.backup_progress_event.emit(
- f"[{self.params['profile_name']}] "
- + translate(
- 'BorgCreateJob',
- 'Backup finished with warnings. See the logs for details.',
- ).format(config.LOG_DIR.as_uri())
- )
- else:
- self.app.backup_log_event.emit('', {})
- self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Backup finished.')}")
-
def progress_event(self, fmt):
self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {fmt}")
@@ -64,7 +52,17 @@ def started_event(self):
def finished_event(self, result):
self.app.backup_finished_event.emit(result)
self.result.emit(result)
- self.pre_post_backup_cmd(self.params, cmd='post_backup_cmd', returncode=result['returncode'])
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgCreateJob', 'Backup finished with errors. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+ else:
+ self.app.backup_log_event.emit('', {})
+ self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Backup finished.')}")
+ self.pre_post_backup_cmd(self.params, cmd='post_backup_cmd', returncode=result['returncode'])
@classmethod
def pre_post_backup_cmd(cls, params, cmd='pre_backup_cmd', returncode=0):
diff --git a/src/vorta/borg/delete.py b/src/vorta/borg/delete.py
index 5b1fe72f0..0dbb69dc5 100644
--- a/src/vorta/borg/delete.py
+++ b/src/vorta/borg/delete.py
@@ -1,5 +1,7 @@
from typing import List
+from vorta import config
+from vorta.i18n import translate
from vorta.store.models import RepoModel
from vorta.utils import borg_compat
@@ -22,7 +24,15 @@ def finished_event(self, result):
self.app.backup_finished_event.emit(result)
self.result.emit(result)
- self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Archive deleted.')}")
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgDeleteJob', 'Delete command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+ else:
+ self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Archive deleted.')}")
@classmethod
def prepare(cls, profile, archives: List[str]):
diff --git a/src/vorta/borg/diff.py b/src/vorta/borg/diff.py
index cd09ff815..72f22ae79 100644
--- a/src/vorta/borg/diff.py
+++ b/src/vorta/borg/diff.py
@@ -1,3 +1,5 @@
+from vorta import config
+from vorta.i18n import translate
from vorta.utils import borg_compat
from .borg_job import BorgJob
@@ -12,10 +14,18 @@ def started_event(self):
def finished_event(self, result):
self.app.backup_finished_event.emit(result)
- self.app.backup_progress_event.emit(
- f"[{self.params['profile_name']}] {self.tr('Obtained differences between archives.')}"
- )
self.result.emit(result)
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgDiffJob', 'Diff command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+ else:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] {self.tr('Obtained differences between archives.')}"
+ )
@classmethod
def prepare(cls, profile, archive_name_1, archive_name_2):
diff --git a/src/vorta/borg/extract.py b/src/vorta/borg/extract.py
index 41108721f..3605d9f24 100644
--- a/src/vorta/borg/extract.py
+++ b/src/vorta/borg/extract.py
@@ -2,6 +2,8 @@
from PyQt6.QtCore import QModelIndex, Qt
+from vorta import config
+from vorta.i18n import translate
from vorta.utils import borg_compat
from vorta.views.extract_dialog import ExtractTree, FileData
from vorta.views.partials.treemodel import FileSystemItem, path_to_str
@@ -19,9 +21,17 @@ def started_event(self):
def finished_event(self, result):
self.app.backup_finished_event.emit(result)
self.result.emit(result)
- self.app.backup_progress_event.emit(
- f"[{self.params['profile_name']}] {self.tr('Restored files from archive.')}"
- )
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgExtractJob', 'Extract command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+ else:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] {self.tr('Restored files from archive.')}"
+ )
@classmethod
def prepare(cls, profile, archive_name, model: ExtractTree, destination_folder):
diff --git a/src/vorta/borg/info_archive.py b/src/vorta/borg/info_archive.py
index afb94b2f3..763f6e9da 100644
--- a/src/vorta/borg/info_archive.py
+++ b/src/vorta/borg/info_archive.py
@@ -1,3 +1,5 @@
+from vorta import config
+from vorta.i18n import translate
from vorta.store.models import ArchiveModel, RepoModel
from vorta.utils import borg_compat
@@ -12,7 +14,17 @@ def started_event(self):
def finished_event(self, result):
self.app.backup_finished_event.emit(result)
self.result.emit(result)
- self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Refreshing archive done.')}")
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgInfoArchiveJob', 'Info command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+ else:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] {self.tr('Refreshing archive done.')}"
+ )
@classmethod
def prepare(cls, profile, archive_name):
diff --git a/src/vorta/borg/mount.py b/src/vorta/borg/mount.py
index 64e99a4ab..7e3b235dd 100644
--- a/src/vorta/borg/mount.py
+++ b/src/vorta/borg/mount.py
@@ -1,6 +1,8 @@
import logging
import os
+from vorta import config
+from vorta.i18n import translate
from vorta.store.models import SettingsModel
from vorta.utils import SHELL_PATTERN_ELEMENT, borg_compat
@@ -13,6 +15,16 @@ class BorgMountJob(BorgJob):
def started_event(self):
self.updated.emit(self.tr('Mounting archive into folderā¦'))
+ def finished_event(self, result):
+ self.result.emit(result)
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgMountJob', 'Mount command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+
@classmethod
def prepare(cls, profile, archive: str = None):
ret = super().prepare(profile)
diff --git a/src/vorta/borg/prune.py b/src/vorta/borg/prune.py
index aba888fb3..aa48e4200 100644
--- a/src/vorta/borg/prune.py
+++ b/src/vorta/borg/prune.py
@@ -1,3 +1,5 @@
+from vorta import config
+from vorta.i18n import translate
from vorta.store.models import RepoModel
from vorta.utils import borg_compat, format_archive_name
@@ -20,7 +22,15 @@ def finished_event(self, result):
self.app.backup_finished_event.emit(result)
self.result.emit(result)
- self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Pruning done.')}")
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgPruneJob', 'Prune command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+ else:
+ self.app.backup_progress_event.emit(f"[{self.params['profile_name']}] {self.tr('Pruning done.')}")
@classmethod
def prepare(cls, profile):
diff --git a/src/vorta/borg/umount.py b/src/vorta/borg/umount.py
index cdec6e25d..7f6d8ebe0 100644
--- a/src/vorta/borg/umount.py
+++ b/src/vorta/borg/umount.py
@@ -2,6 +2,9 @@
import psutil
+from vorta import config
+from vorta.i18n import translate
+
from ..i18n import trans_late
from .borg_job import BorgJob
@@ -10,6 +13,16 @@ class BorgUmountJob(BorgJob):
def started_event(self):
self.updated.emit(self.tr('Unmounting archiveā¦'))
+ def finished_event(self, result):
+ self.result.emit(result)
+ if result['returncode'] != 0:
+ self.app.backup_progress_event.emit(
+ f"[{self.params['profile_name']}] "
+ + translate(
+ 'BorgMountJob', 'Umount command has failed. See the logs for details.'
+ ).format(config.LOG_DIR.as_uri())
+ )
+
@classmethod
def prepare(cls, profile, mount_point, archive_name=None):
ret = super().prepare(profile)
diff --git a/src/vorta/scheduler.py b/src/vorta/scheduler.py
index 9ff19681c..a90f1a5ba 100644
--- a/src/vorta/scheduler.py
+++ b/src/vorta/scheduler.py
@@ -348,7 +348,7 @@ def set_timer_for_profile(self, profile_id: int):
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,
diff --git a/src/vorta/store/settings.py b/src/vorta/store/settings.py
index 03a1cc543..6f0e3ee54 100644
--- a/src/vorta/store/settings.py
+++ b/src/vorta/store/settings.py
@@ -177,7 +177,7 @@ def get_misc_settings() -> List[Dict[str, str]]:
'type': 'checkbox',
'label': trans_late(
'settings',
- "If the system tray isn't available, " "ask whether to continue in the background " "on exit",
+ "If the system tray isn't available, ask whether to continue in the background on exit",
),
},
{