Skip to content

A better way to show Borg's errors #2221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/vorta/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions src/vorta/borg/borg_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -299,23 +300,26 @@ 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:
time.sleep(0.1)
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,
Expand Down
6 changes: 3 additions & 3 deletions src/vorta/borg/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="{0}">logs</a> for details.').format(
config.LOG_DIR.as_uri()
)
+ translate(
'RepoCheckJob', 'Repo check command has failed. See the <a href="{0}">logs</a> for details.'
).format(config.LOG_DIR.as_uri())
)
self.app.check_failed_event.emit(result)
else:
Expand Down
24 changes: 11 additions & 13 deletions src/vorta/borg/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="{0}">logs</a> 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}")

Expand All @@ -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 <a href="{0}">logs</a> 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):
Expand Down
12 changes: 11 additions & 1 deletion src/vorta/borg/delete.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 <a href="{0}">logs</a> 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]):
Expand Down
16 changes: 13 additions & 3 deletions src/vorta/borg/diff.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from vorta import config
from vorta.i18n import translate
from vorta.utils import borg_compat

from .borg_job import BorgJob
Expand All @@ -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 <a href="{0}">logs</a> 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):
Expand Down
16 changes: 13 additions & 3 deletions src/vorta/borg/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <a href="{0}">logs</a> 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):
Expand Down
14 changes: 13 additions & 1 deletion src/vorta/borg/info_archive.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 <a href="{0}">logs</a> 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):
Expand Down
12 changes: 12 additions & 0 deletions src/vorta/borg/mount.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 <a href="{0}">logs</a> for details.'
).format(config.LOG_DIR.as_uri())
)

@classmethod
def prepare(cls, profile, archive: str = None):
ret = super().prepare(profile)
Expand Down
12 changes: 11 additions & 1 deletion src/vorta/borg/prune.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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 <a href="{0}">logs</a> 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):
Expand Down
13 changes: 13 additions & 0 deletions src/vorta/borg/umount.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 <a href="{0}">logs</a> for details.'
).format(config.LOG_DIR.as_uri())
)

@classmethod
def prepare(cls, profile, mount_point, archive_name=None):
ret = super().prepare(profile)
Expand Down
2 changes: 1 addition & 1 deletion src/vorta/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/vorta/store/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
},
{
Expand Down