mirror of https://github.com/borgbase/vorta
Add helper class to manage Borg version and supported features. (#250)
* Add helper class to manage Borg version and supported features. Fixes #205 * Add setuptools as dependency to ensure pkg_resources is available. * Review fixes, check for ZStd support, refactor some relative imports. * Repo-add dialog: Disable Blake2 options if not available. * When disabling compression algos, select by value, not index.
This commit is contained in:
parent
15aebc6ceb
commit
b9486d003c
|
@ -39,6 +39,7 @@ install_requires =
|
|||
apscheduler
|
||||
psutil
|
||||
qdarkstyle
|
||||
setuptools
|
||||
secretstorage; sys_platform != 'darwin'
|
||||
pyobjc-core; sys_platform == 'darwin'
|
||||
pyobjc-framework-Cocoa; sys_platform == 'darwin'
|
||||
|
|
|
@ -10,7 +10,7 @@ from .models import BackupProfileModel, SettingsModel
|
|||
from .qt_single_application import QtSingleApplication
|
||||
from .scheduler import VortaScheduler
|
||||
from .tray_menu import TrayMenu
|
||||
from .utils import parse_args, set_tray_icon
|
||||
from .utils import parse_args, set_tray_icon, borg_compat
|
||||
from .views.main_window import MainWindow
|
||||
from vorta.borg.version import BorgVersionThread
|
||||
|
||||
|
@ -30,7 +30,6 @@ class VortaApp(QtSingleApplication):
|
|||
backup_finished_event = QtCore.pyqtSignal(dict)
|
||||
backup_cancelled_event = QtCore.pyqtSignal()
|
||||
backup_log_event = QtCore.pyqtSignal(str)
|
||||
borg_details = {'version': '0.0', 'path': 'borg'}
|
||||
|
||||
def __init__(self, args_raw, single_app=False):
|
||||
|
||||
|
@ -112,7 +111,7 @@ class VortaApp(QtSingleApplication):
|
|||
thread.start()
|
||||
|
||||
def set_borg_details_result(self, result):
|
||||
self.borg_details['version'] = result['data']['version']
|
||||
self.borg_details['path'] = result['data']['path']
|
||||
borg_compat.set_version(result['data']['version'], result['data']['path'])
|
||||
if self._main_window_exists():
|
||||
self.main_window.miscTab.set_borg_details(self.borg_details)
|
||||
self.main_window.miscTab.set_borg_details(borg_compat.version, borg_compat.path)
|
||||
self.main_window.repoTab.toggle_available_compression()
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
from pkg_resources import parse_version
|
||||
|
||||
MIN_BORG_FOR_FEATURE = {
|
||||
'BLAKE2': parse_version('1.1.4'),
|
||||
'ZSTD': parse_version('1.1.4'),
|
||||
# add new version-checks here.
|
||||
}
|
||||
|
||||
|
||||
class BorgCompatibility:
|
||||
"""
|
||||
An internal class that keeps details of the Borg version
|
||||
in use and allows checking for specific features. Could be used
|
||||
to customize Borg commands by version in the future.
|
||||
"""
|
||||
|
||||
version = '0.0'
|
||||
path = ''
|
||||
|
||||
def set_version(self, version, path):
|
||||
self.version = version
|
||||
self.path = path
|
||||
|
||||
def check(self, feature_name):
|
||||
return parse_version(self.version) >= MIN_BORG_FOR_FEATURE[feature_name]
|
|
@ -3,9 +3,9 @@ import tempfile
|
|||
from dateutil import parser
|
||||
import subprocess
|
||||
|
||||
from ..i18n import trans_late
|
||||
from ..utils import get_current_wifi, format_archive_name
|
||||
from ..models import SourceFileModel, ArchiveModel, WifiSettingModel, RepoModel
|
||||
from vorta.i18n import trans_late
|
||||
from vorta.utils import get_current_wifi, format_archive_name, borg_compat
|
||||
from vorta.models import SourceFileModel, ArchiveModel, WifiSettingModel, RepoModel
|
||||
from .borg_thread import BorgThread
|
||||
|
||||
|
||||
|
@ -97,6 +97,10 @@ class BorgCreateThread(BorgThread):
|
|||
ret['message'] = trans_late('messages', 'Repo folder not mounted or moved.')
|
||||
return ret
|
||||
|
||||
if 'zstd' in profile.compression and not borg_compat.check('ZSTD'):
|
||||
ret['message'] = trans_late('messages', 'Your current Borg version does not support ZStd compression.')
|
||||
return ret
|
||||
|
||||
cmd = ['borg', 'create', '--list', '--info', '--log-json', '--json', '--filter=AM', '-C', profile.compression]
|
||||
|
||||
# Add excludes
|
||||
|
|
|
@ -22,10 +22,13 @@ from PyQt5.QtGui import QIcon
|
|||
from PyQt5 import QtCore
|
||||
from vorta.keyring.abc import VortaKeyring
|
||||
from vorta.log import logger
|
||||
from vorta.borg._compatibility import BorgCompatibility
|
||||
|
||||
keyring = VortaKeyring.get_keyring()
|
||||
logger.info('Using %s Keyring implementation.', keyring.__class__.__name__)
|
||||
|
||||
borg_compat = BorgCompatibility()
|
||||
|
||||
|
||||
def nested_dict():
|
||||
"""
|
||||
|
|
|
@ -2,17 +2,17 @@ import sys
|
|||
from PyQt5.QtWidgets import QShortcut
|
||||
from PyQt5 import uic, QtCore
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
|
||||
from vorta.utils import get_asset, borg_compat
|
||||
from vorta.models import BackupProfileModel
|
||||
from vorta.borg.borg_thread import BorgThread
|
||||
from vorta.views.utils import get_theme_class
|
||||
from .repo_tab import RepoTab
|
||||
from .source_tab import SourceTab
|
||||
from .archive_tab import ArchiveTab
|
||||
from .schedule_tab import ScheduleTab
|
||||
from .misc_tab import MiscTab
|
||||
from .profile_add_edit_dialog import AddProfileWindow, EditProfileWindow
|
||||
from ..utils import get_asset
|
||||
from ..models import BackupProfileModel
|
||||
from vorta.borg.borg_thread import BorgThread
|
||||
from vorta.views.utils import get_theme_class
|
||||
|
||||
|
||||
uifile = get_asset('UI/mainwindow.ui')
|
||||
MainWindowUI, MainWindowBase = uic.loadUiType(uifile, from_imports=True, import_from=get_theme_class())
|
||||
|
@ -34,7 +34,7 @@ class MainWindow(MainWindowBase, MainWindowUI):
|
|||
self.archiveTab = ArchiveTab(self.archiveTabSlot)
|
||||
self.scheduleTab = ScheduleTab(self.scheduleTabSlot)
|
||||
self.miscTab = MiscTab(self.miscTabSlot)
|
||||
self.miscTab.set_borg_details(self.app.borg_details)
|
||||
self.miscTab.set_borg_details(borg_compat.version, borg_compat.path)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
|
||||
self.repoTab.repo_changed.connect(self.archiveTab.populate_from_profile)
|
||||
|
|
|
@ -39,6 +39,6 @@ class MiscTab(MiscTabBase, MiscTabUI, BackupProfileMixin):
|
|||
if key == 'autostart':
|
||||
open_app_at_startup(new_value)
|
||||
|
||||
def set_borg_details(self, borg_details):
|
||||
self.borgVersion.setText(borg_details['version'])
|
||||
self.borgPath.setText(borg_details['path'])
|
||||
def set_borg_details(self, version, path):
|
||||
self.borgVersion.setText(version)
|
||||
self.borgPath.setText(path)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import re
|
||||
from PyQt5 import uic
|
||||
|
||||
from ..utils import get_private_keys, get_asset, choose_file_dialog
|
||||
from vorta.utils import get_private_keys, get_asset, choose_file_dialog, borg_compat
|
||||
from vorta.borg.init import BorgInitThread
|
||||
from vorta.borg.info import BorgInfoThread
|
||||
from vorta.views.utils import get_theme_class
|
||||
|
@ -85,16 +85,21 @@ class AddRepoWindow(AddRepoBase, AddRepoUI):
|
|||
self._set_status(self.tr('Unable to add your repository.'))
|
||||
|
||||
def init_encryption(self):
|
||||
self.encryptionComboBox.addItem(self.tr('Repokey-Blake2 (Recommended, key stored in repository)'),
|
||||
'repokey-blake2')
|
||||
self.encryptionComboBox.addItem(self.tr('Repokey'),
|
||||
'repokey')
|
||||
self.encryptionComboBox.addItem(self.tr('Keyfile-Blake2 (Key stored in home directory)'),
|
||||
'keyfile-blake2')
|
||||
self.encryptionComboBox.addItem(self.tr('Keyfile'),
|
||||
'keyfile')
|
||||
self.encryptionComboBox.addItem(self.tr('None (not recommended)'),
|
||||
'none')
|
||||
encryption_algos = [
|
||||
['Repokey-Blake2 (Recommended, key stored in repository)', 'repokey-blake2'],
|
||||
['Repokey', 'repokey'],
|
||||
['Keyfile-Blake2 (Key stored in home directory)', 'keyfile-blake2'],
|
||||
['Keyfile', 'keyfile'],
|
||||
['None (not recommended)', 'none']
|
||||
]
|
||||
|
||||
for desc, name in encryption_algos:
|
||||
self.encryptionComboBox.addItem(self.tr(desc), name)
|
||||
|
||||
if not borg_compat.check('BLAKE2'):
|
||||
self.encryptionComboBox.model().item(0).setEnabled(False)
|
||||
self.encryptionComboBox.model().item(2).setEnabled(False)
|
||||
self.encryptionComboBox.setCurrentIndex(1)
|
||||
|
||||
def init_ssh_key(self):
|
||||
keys = get_private_keys()
|
||||
|
|
|
@ -2,11 +2,11 @@ import os
|
|||
from PyQt5 import uic, QtCore
|
||||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
from ..models import RepoModel, ArchiveModel, BackupProfileMixin
|
||||
from .repo_add_dialog import AddRepoWindow, ExistingRepoWindow
|
||||
from ..utils import pretty_bytes, get_private_keys, get_asset
|
||||
from vorta.models import RepoModel, ArchiveModel, BackupProfileMixin
|
||||
from vorta.utils import pretty_bytes, get_private_keys, get_asset, borg_compat
|
||||
from .ssh_dialog import SSHAddWindow
|
||||
from vorta.views.utils import get_theme_class
|
||||
from .repo_add_dialog import AddRepoWindow, ExistingRepoWindow
|
||||
from .utils import get_theme_class
|
||||
|
||||
uifile = get_asset('UI/repotab.ui')
|
||||
RepoUI, RepoBase = uic.loadUiType(uifile, from_imports=True, import_from=get_theme_class())
|
||||
|
@ -39,6 +39,7 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
|
|||
self.repoCompression.addItem(self.tr('LZ4 (modern, default)'), 'lz4')
|
||||
self.repoCompression.addItem(self.tr('Zstandard Level 3 (modern)'), 'zstd,3')
|
||||
self.repoCompression.addItem(self.tr('Zstandard Level 8 (modern)'), 'zstd,8')
|
||||
|
||||
# zlib and lzma come from python stdlib and are there (and in borg) since long.
|
||||
# but maybe not much reason to start with these nowadays, considering zstd supports
|
||||
# a very wide range of compression levels and has great speed. if speed is more
|
||||
|
@ -48,6 +49,9 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
|
|||
self.repoCompression.addItem(self.tr('No Compression'), 'none')
|
||||
self.repoCompression.currentIndexChanged.connect(self.compression_select_action)
|
||||
|
||||
self.toggle_available_compression()
|
||||
self.repoCompression.currentIndexChanged.connect(self.compression_select_action)
|
||||
|
||||
self.init_ssh()
|
||||
self.sshComboBox.currentIndexChanged.connect(self.ssh_select_action)
|
||||
self.sshKeyToClipboardButton.clicked.connect(self.ssh_copy_to_clipboard_action)
|
||||
|
@ -88,6 +92,12 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
|
|||
for key in keys:
|
||||
self.sshComboBox.addItem(f'{key["filename"]} ({key["format"]})', key['filename'])
|
||||
|
||||
def toggle_available_compression(self):
|
||||
use_zstd = borg_compat.check('ZSTD')
|
||||
for algo in ['zstd,3', 'zstd,8']:
|
||||
ix = self.repoCompression.findData(algo)
|
||||
self.repoCompression.model().item(ix).setEnabled(use_zstd)
|
||||
|
||||
def ssh_select_action(self, index):
|
||||
if index == 1:
|
||||
ssh_add_window = SSHAddWindow()
|
||||
|
|
Loading…
Reference in New Issue