From 2c7233ce45ebc9045425fc0f1c0199e8e48fae12 Mon Sep 17 00:00:00 2001 From: Fuxino Date: Sat, 14 Nov 2026 11:08:21 +0100 Subject: [PATCH] Keep the progress bar estimate when the last file count is exceeded When a backup has more files than the last one, estimate the total from the files rsync has found so far, capped at 99% until the scan ends, instead of falling back to "(scanning)". --- man/simple_backup.1 | 5 +++-- simple_backup/simple_backup.py | 13 ++++++++----- tests/test_local.py | 25 ++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/man/simple_backup.1 b/man/simple_backup.1 index bd75d01..55658ed 100644 --- a/man/simple_backup.1 +++ b/man/simple_backup.1 @@ -117,8 +117,9 @@ Don't use systemd journal for logging. Don't show a progress bar while rsync copies the files. The progress bar is only shown when the output is a terminal, so it never appears in the systemd journal or in redirected output. While rsync is still looking for files, the percentage and estimated time (marked with -\(aq~\(aq) are based on the number of files in the last backup, or the bar shows -\(aq(scanning)\(aq if that is unknown or already exceeded. +\(aq~\(aq) are based on the number of files in the last backup (or on the files found so far, +once this backup has more), and stay below 100%. Without a previous count the bar shows +\(aq(scanning)\(aq instead. .TP .B \-\-rsync\-options OPTIONS [OPTION...] By default, the following rsync options are used: diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index 40691fa..7319510 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -135,13 +135,16 @@ class _ProgressBar: percent: int | None if self.total is None: - # Estimate from the last backup, unless this one already has more files - if 0 < self._checked < self._estimate: - percent = self._checked * 100 // self._estimate - text = f'{f"~{percent}":>3}% {self._checked:,}/~{self._estimate:,} files {size} copied' + # Estimate from the last backup; past it, from the files found so far, as the scan is then nearly done + estimate = max(self._estimate, self._total) + + if self._estimate > 0 and self._checked > 0: + # The real total is still unknown, so never claim 100% + percent = min(self._checked * 100 // estimate, 99) + text = f'{f"~{percent}":>3}% {self._checked:,}/~{estimate:,} files {size} copied' if percent > 0: - text += f' ETA ~{self._eta(self._estimate)}' + text += f' ETA ~{self._eta(estimate)}' else: text = f'{self._checked:,} files checked {size} copied (scanning)' percent = None diff --git a/tests/test_local.py b/tests/test_local.py index c062c11..eb1a465 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -303,12 +303,31 @@ class TestProgressBar: assert capsys.readouterr().err == \ '\r[#######-----------------------] ~25% 1,000/~4,000 files 2.00M copied ETA ~0:01:30\033[K' - def test_estimate_exceeded(self, capsys: pytest.CaptureFixture[str]) -> None: + def test_estimate_exceeded(self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: + monkeypatch.setattr(sb, 'get_terminal_size', lambda: os.terminal_size((100, 24))) bar = sb._ProgressBar(estimate=1000) - # More files than in the last backup: back to plain scanning + # More files than in the last backup: estimate from the files found so far assert bar.update(' 2.00M 25% 1.47MB/s 0:00:02 (xfr#1, ir-chk=500/2000)') - assert capsys.readouterr().err == '\r1,500 files checked 2.00M copied (scanning)\033[K' + assert capsys.readouterr().err == \ + '\r[######################--------] ~75% 1,500/~2,000 files 2.00M copied ETA ~0:00:10\033[K' + + def test_estimate_capped_while_scanning(self, monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str]) -> None: + monkeypatch.setattr(sb, 'get_terminal_size', lambda: os.terminal_size((100, 24))) + bar = sb._ProgressBar(estimate=1000) + + # All files found so far are checked, but rsync may still find more + assert bar.update(' 2.00M 25% 1.47MB/s 0:00:02 (xfr#1, ir-chk=0/2000)') + assert bar.total is None + assert ' ~99% 2,000/~2,000 files 2.00M copied ETA ~0:00:00\033[K' in capsys.readouterr().err + + def test_no_estimate_without_last_count(self, capsys: pytest.CaptureFixture[str]) -> None: + bar = sb._ProgressBar() + + # The files found so far are only a guess at the total near the end of the scan + assert bar.update(' 2.00M 25% 1.47MB/s 0:00:02 (xfr#1, ir-chk=0/2000)') + assert capsys.readouterr().err == '\r2,000 files checked 2.00M copied (scanning)\033[K' def test_real_total_replaces_estimate(self, capsys: pytest.CaptureFixture[str]) -> None: bar = sb._ProgressBar(estimate=4000)