1
0
Fork 0
mirror of https://github.com/borgbase/vorta synced 2024-12-21 23:33:13 +00:00

Setting for number format in archive tab. By @bigtedde (#1719)

This commit is contained in:
Ted Lawson 2023-08-11 04:22:10 -07:00 committed by GitHub
parent b015368fee
commit b58ffb6aed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 126 additions and 95 deletions

View file

@ -59,6 +59,18 @@ def get_misc_settings() -> List[Dict[str, str]]:
'label': trans_late('settings', 'Get statistics of file/folder when added'),
'tooltip': trans_late('settings', 'When adding a new source, calculate its size and the number of files.'),
},
{
'key': 'enable_fixed_units',
'value': False,
'type': 'checkbox',
'group': information,
'label': trans_late('settings', 'Use the same unit of measurement for archive sizes'),
'tooltip': trans_late(
'settings',
'When enabled, all archive sizes will use the same unit of measurement, '
'such as KB or MB. This can make archive sizes easier to compare.',
),
},
{
'key': 'use_system_keyring',
'value': True,

View file

@ -24,6 +24,8 @@
# Used to store whether a user wanted to override the
# default directory for the --development flag
DEFAULT_DIR_FLAG = object()
METRIC_UNITS = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
NONMETRIC_UNITS = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi']
borg_compat = BorgCompatibility()
_network_status_monitor = None
@ -140,14 +142,10 @@ def get_network_status_monitor():
def get_path_datasize(path, exclude_patterns):
file_info = QFileInfo(path)
data_size = 0
if file_info.isDir():
data_size, files_count = get_directory_size(file_info.absoluteFilePath(), exclude_patterns)
# logger.info("path (folder) %s %u elements size now=%u (%s)",
# file_info.absoluteFilePath(), files_count, data_size, pretty_bytes(data_size))
else:
# logger.info("path (file) %s size=%u", file_info.path(), file_info.size())
data_size = file_info.size()
files_count = 1
@ -279,11 +277,7 @@ def pretty_bytes(
if not isinstance(size, int):
return ''
prefix = '+' if sign and size > 0 else ''
power, units = (
(10**3, ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'])
if metric
else (2**10, ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'])
)
power, units = (10**3, METRIC_UNITS) if metric else (2**10, NONMETRIC_UNITS)
if fixed_unit is None:
n = find_best_unit_for_size(size, metric=metric, precision=precision)
else:

View file

@ -33,7 +33,7 @@
from vorta.borg.rename import BorgRenameJob
from vorta.borg.umount import BorgUmountJob
from vorta.i18n import translate
from vorta.store.models import ArchiveModel, BackupProfileMixin
from vorta.store.models import ArchiveModel, BackupProfileMixin, SettingsModel
from vorta.utils import (
borg_compat,
choose_file_dialog,
@ -291,9 +291,12 @@ def populate_from_profile(self):
formatted_time = archive.time.strftime('%Y-%m-%d %H:%M')
self.archiveTable.setItem(row, 0, QTableWidgetItem(formatted_time))
self.archiveTable.setItem(
row, 1, SizeItem(pretty_bytes(archive.size, fixed_unit=best_unit, precision=SIZE_DECIMAL_DIGITS))
)
# format units based on user settings for 'dynamic' or 'fixed' units
fixed_unit = best_unit if SettingsModel.get(key='enable_fixed_units').value else None
size = pretty_bytes(archive.size, fixed_unit=fixed_unit, precision=SIZE_DECIMAL_DIGITS)
self.archiveTable.setItem(row, 1, SizeItem(size))
if archive.duration is not None:
formatted_duration = str(timedelta(seconds=round(archive.duration)))
else:

View file

@ -78,6 +78,7 @@ def __init__(self, parent=None):
self.repoTab.repo_changed.connect(self.archiveTab.populate_from_profile)
self.repoTab.repo_changed.connect(self.scheduleTab.populate_from_profile)
self.repoTab.repo_added.connect(self.archiveTab.refresh_archive_list)
self.miscTab.refresh_archive.connect(self.archiveTab.populate_from_profile)
self.createStartBtn.clicked.connect(self.app.create_backup_action)
self.cancelButton.clicked.connect(self.app.backup_cancelled_event.emit)

View file

@ -1,6 +1,6 @@
import logging
from PyQt6 import uic
from PyQt6 import QtCore, uic
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication,
@ -28,6 +28,8 @@
class MiscTab(MiscTabBase, MiscTabUI, BackupProfileMixin):
refresh_archive = QtCore.pyqtSignal()
def __init__(self, parent=None):
"""Init."""
super().__init__(parent)
@ -101,6 +103,8 @@ def populate(self):
cb.setCheckState(Qt.CheckState(setting.value))
cb.setTristate(False)
cb.stateChanged.connect(lambda v, key=setting.key: self.save_setting(key, v))
if setting.key == 'enable_fixed_units':
cb.stateChanged.connect(self.refresh_archive.emit)
tb = ToolTipButton()
tb.setToolTip(setting.tooltip)

View file

@ -11,7 +11,6 @@
def test_autostart(qapp, qtbot):
"""Check if file exists only on Linux, otherwise just check it doesn't crash"""
setting = "Automatically start Vorta at login"
_click_toggle_setting(setting, qapp, qtbot)
@ -34,10 +33,41 @@ def test_autostart(qapp, qtbot):
assert not os.path.exists(autostart_path)
def test_enable_fixed_units(qapp, qtbot, mocker):
"""Tests the 'enable fixed units' setting to ensure the archive tab sizes are displayed correctly."""
tab = qapp.main_window.archiveTab
setting = "Use the same unit of measurement for archive sizes"
# set mocks
mock_setting = mocker.patch.object(vorta.views.archive_tab.SettingsModel, "get", return_value=Mock(value=True))
mock_pretty_bytes = mocker.patch.object(vorta.views.archive_tab, "pretty_bytes")
# with setting enabled, fixed units should be determined and passed to pretty_bytes as an 'int'
tab.populate_from_profile()
mock_pretty_bytes.assert_called()
kwargs_list = mock_pretty_bytes.call_args_list[0].kwargs
assert 'fixed_unit' in kwargs_list
assert isinstance(kwargs_list['fixed_unit'], int)
# disable setting and reset mock
mock_setting.return_value = Mock(value=False)
mock_pretty_bytes.reset_mock()
# with setting disabled, pretty_bytes should be called with fixed units set to 'None'
tab.populate_from_profile()
mock_pretty_bytes.assert_called()
kwargs_list = mock_pretty_bytes.call_args_list[0].kwargs
assert 'fixed_unit' in kwargs_list
assert kwargs_list['fixed_unit'] is None
# use the qt bot to click the setting and see that the refresh_archive emit works as intended.
with qtbot.waitSignal(qapp.main_window.miscTab.refresh_archive, timeout=5000):
_click_toggle_setting(setting, qapp, qtbot)
@pytest.mark.skipif(sys.platform != 'darwin', reason="Full Disk Access check only on Darwin")
def test_check_full_disk_access(qapp, qtbot, mocker):
"""Enables/disables 'Check for Full Disk Access on startup' setting and ensures functionality"""
setting = "Check for Full Disk Access on startup"
# Set mocks for setting enabled
@ -64,23 +94,16 @@ def test_check_full_disk_access(qapp, qtbot, mocker):
def _click_toggle_setting(setting, qapp, qtbot):
"""Click toggle setting in the misc tab"""
"""Toggle setting checkbox in the misc tab"""
miscTab = qapp.main_window.miscTab
main = qapp.main_window
main.tabWidget.setCurrentIndex(4)
tab = main.miscTab
for x in range(0, tab.checkboxLayout.count()):
item = tab.checkboxLayout.itemAt(x, QFormLayout.ItemRole.FieldRole)
if not item:
continue
checkbox = item.itemAt(0).widget()
checkbox.__class__ = QCheckBox
if checkbox.text() == setting:
# Have to use pos to click checkbox correctly
# https://stackoverflow.com/questions/19418125/pysides-qtest-not-checking-box/24070484#24070484
qtbot.mouseClick(
checkbox, QtCore.Qt.MouseButton.LeftButton, pos=QtCore.QPoint(2, int(checkbox.height() / 2))
)
break
for x in range(miscTab.checkboxLayout.count()):
item = miscTab.checkboxLayout.itemAt(x, QFormLayout.ItemRole.FieldRole)
if item is not None:
checkbox = item.itemAt(0).widget()
if checkbox.text() == setting and isinstance(checkbox, QCheckBox):
# Have to use pos to click checkbox correctly
# https://stackoverflow.com/questions/19418125/pysides-qtest-not-checking-box/24070484#24070484
pos = QtCore.QPoint(2, int(checkbox.height() / 2))
qtbot.mouseClick(checkbox, QtCore.Qt.MouseButton.LeftButton, pos=pos)
break

View file

@ -1,7 +1,11 @@
import uuid
import pytest
from vorta.keyring.abc import VortaKeyring
from vorta.utils import find_best_unit_for_sizes, pretty_bytes
from vorta.utils import (
find_best_unit_for_sizes,
pretty_bytes,
)
def test_keyring():
@ -13,70 +17,60 @@ def test_keyring():
assert keyring.get_password("vorta-repo", REPO) == UNICODE_PW
def test_best_size_unit_precision0():
@pytest.mark.parametrize(
"precision, expected_unit",
[
(0, 1), # return units as "1" (represents KB), min=100KB
(1, 2), # return units as "2" (represents MB), min=0.1MB
(2, 2), # still returns KB, since 0.1MB < min=0.001 GB to allow for GB to be best_unit
],
)
def test_best_unit_for_sizes_precision(precision, expected_unit):
MB = 1000000
sizes = [int(0.1 * MB), 100 * MB, 2000 * MB]
unit = find_best_unit_for_sizes(sizes, metric=True, precision=0)
assert unit == 1 # KB, min=100KB
best_unit = find_best_unit_for_sizes(sizes, metric=True, precision=precision)
assert best_unit == expected_unit
def test_best_size_unit_precision1():
MB = 1000000
sizes = [int(0.1 * MB), 100 * MB, 2000 * MB]
unit = find_best_unit_for_sizes(sizes, metric=True, precision=1)
assert unit == 2 # MB, min=0.1MB
@pytest.mark.parametrize(
"sizes, expected_unit",
[
([], 0), # no sizes given but should still return "0" (represents bytes) as best representation
([102], 0), # non-metric size 102 < 0.1KB (102 < 0.1 * 1024), so it will return 0 instead of 1
([103], 1), # non-metric size 103 > 0.1KB (103 < 0.1 * 1024), so it will return 1
],
)
def test_best_unit_for_sizes_nonmetric(sizes, expected_unit):
best_unit = find_best_unit_for_sizes(sizes, metric=False, precision=1)
assert best_unit == expected_unit
def test_best_size_unit_empty():
sizes = []
unit = find_best_unit_for_sizes(sizes, metric=True, precision=1)
assert unit == 0 # bytes
@pytest.mark.parametrize(
"size, metric, precision, fixed_unit, expected_output",
[
(10**5, True, 1, 2, "0.1 MB"), # 100KB, metric, precision 1, fixed unit "2" (MB)
(10**6, True, 0, 2, "1 MB"), # 1MB, metric, precision 0, fixed unit "2" (MB)
(10**6, True, 1, 2, "1.0 MB"), # 1MB, metric, precision 1, fixed unit "2" (MB)
(1024 * 1024, False, 1, 2, "1.0 MiB"), # 1MiB, nonmetric, precision 1, fixed unit "2" (MiB)
],
)
def test_pretty_bytes_fixed_units(size, metric, precision, fixed_unit, expected_output):
# test pretty bytes when specifying a fixed unit of measurement
output = pretty_bytes(size, metric=metric, precision=precision, fixed_unit=fixed_unit)
assert output == expected_output
def test_best_size_unit_precision3():
MB = 1000000
sizes = [1 * MB, 100 * MB, 2000 * MB]
unit = find_best_unit_for_sizes(sizes, metric=True, precision=3)
assert unit == 3 # GB, min=0.001 GB
def test_best_size_unit_nonmetric1():
sizes = [102]
unit = find_best_unit_for_sizes(sizes, metric=False, precision=1)
assert unit == 0 # 102 < 0.1KB
def test_best_size_unit_nonmetric2():
sizes = [103]
unit = find_best_unit_for_sizes(sizes, metric=False, precision=1)
assert unit == 1 # 103bytes == 0.1KB
def test_pretty_bytes_metric_fixed1():
s = pretty_bytes(1000000, metric=True, precision=0, fixed_unit=2)
assert s == "1 MB"
def test_pretty_bytes_metric_fixed2():
s = pretty_bytes(1000000, metric=True, precision=1, fixed_unit=2)
assert s == "1.0 MB"
def test_pretty_bytes_metric_fixed3():
s = pretty_bytes(100000, metric=True, precision=1, fixed_unit=2)
assert s == "0.1 MB"
def test_pretty_bytes_nonmetric_fixed1():
s = pretty_bytes(1024 * 1024, metric=False, precision=1, fixed_unit=2)
assert s == "1.0 MiB"
def test_pretty_bytes_metric_nonfixed2():
s = pretty_bytes(1000000, metric=True, precision=1)
assert s == "1.0 MB"
def test_pretty_bytes_metric_large():
s = pretty_bytes(10**30, metric=True, precision=1)
assert s == "1000000.0 YB"
@pytest.mark.parametrize(
"size, metric, expected_output",
[
(10**6, True, "1.0 MB"), # 1MB, metric
(10**24, True, "1.0 YB"), # 1YB, metric
(10**30, True, "1000000.0 YB"), # test huge number, metric
(1024 * 1024, False, "1.0 MiB"), # 1MiB, nonmetric
(2**40 * 2**40, False, "1.0 YiB"), # 1YiB, nonmetric
],
)
def test_pretty_bytes_nonfixed_units(size, metric, expected_output):
# test pretty bytes when NOT specifying a fixed unit of measurement
output = pretty_bytes(size, metric=metric, precision=1)
assert output == expected_output