Skip to content
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
2 changes: 1 addition & 1 deletion src/borg/archiver/check_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def do_check(self, args, repository):
except IntegrityError:
pass # will try to make key later again
if not args.archives_only:
if not repository.check(repair=args.repair, max_duration=args.max_duration):
if not repository.check(repair=args.repair, max_duration=args.max_duration, progress=args.progress):
set_ec(EXIT_WARNING)
if not args.repo_only and not archive_checker.check(
repository,
Expand Down
2 changes: 1 addition & 1 deletion src/borg/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from .process import daemonize, daemonizing, ThreadRunner
from .process import signal_handler, raising_signal_handler, sig_int, ignore_sigint, SigHup, SigTerm
from .process import popen_with_error_handling, is_terminal, prepare_subprocess_env, create_filter_process
from .progress import ProgressIndicatorPercent, ProgressIndicatorMessage
from .progress import ProgressIndicatorPercent, ProgressIndicatorMessage, ProgressIndicatorCounter
from .time import parse_timestamp, timestamp, safe_timestamp, safe_s, safe_ns, MAX_S, SUPPORT_32BIT_PLATFORMS
from .time import format_time, format_timedelta, OutputTimestamp, archive_ts_now
from .yes_no import yes, TRUISH, FALSISH, DEFAULTISH
Expand Down
34 changes: 33 additions & 1 deletion src/borg/helpers/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class ProgressIndicatorBase:
LOGGER = "borg.output.progress"
JSON_TYPE: str = None
JSON_TYPE: str = None # type: ignore[assignment]

operation_id_counter = 0

Expand Down Expand Up @@ -41,6 +41,38 @@ def output(self, msg):
self.logger.info(j)


class ProgressIndicatorCounter(ProgressIndicatorBase):
JSON_TYPE = "progress_counter"

def __init__(self, step=1000, msg="%d objects", msgid=None):
"""
Activity-based progress indicator, simply tracking a changing count.

:param step: step size
:param msg: output message; must contain one %d placeholder for the count.
"""
self.step = step
self.msg = msg
self.trigger_at = step
super().__init__(msgid=msgid)

def show(self, current=None):
"""
Show and output the progress message if the step condition is met.

:param current: Set the current counter value.
"""
if current is not None and current >= self.trigger_at:
# adjust trigger_at to the next step threshold
while self.trigger_at <= current:
self.trigger_at += self.step
return self.output(self.msg % current)

def output(self, message):
j = self.make_json(message=message)
self.logger.info(j)


class ProgressIndicatorPercent(ProgressIndicatorBase):
JSON_TYPE = "progress_percent"

Expand Down
9 changes: 7 additions & 2 deletions src/borg/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,8 +954,13 @@ def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, v
def info(self):
"""actual remoting is done via self.call in the @api decorator"""

@api(since=parse_version("1.0.0"), max_duration={"since": parse_version("1.2.0a4"), "previously": 0})
def check(self, repair=False, max_duration=0):
@api(
since=parse_version("1.0.0"),
max_duration={"since": parse_version("1.2.0a4"), "previously": 0},
# NOTE: update version when next beta is released
progress={"since": parse_version("2.0.0b21"), "dontcare": True},
)
def check(self, repair=False, max_duration=0, progress=False):
"""actual remoting is done via self.call in the @api decorator"""

@api(
Expand Down
17 changes: 15 additions & 2 deletions src/borg/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def info(self):
info = dict(id=self.id, version=self.version)
return info

def check(self, repair=False, max_duration=0):
def check(self, repair=False, max_duration=0, progress=False):
"""Check repository consistency"""

def log_error(msg):
Expand All @@ -317,7 +317,12 @@ def check_object(obj):
else:
log_error("too small.")

# TODO: progress indicator, ...
if progress:
from .helpers.progress import ProgressIndicatorCounter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if possible, try to avoid doing imports inside loops, rather to them on module level.

check the other code if it also initializes the Progress Indicators only if --progress is given. guess initializing and ending them is no problem, but .show() needs to depend on --progress, of course.


pi = ProgressIndicatorCounter(step=1000, msg="Checked objects: %d", msgid="repository.check")
else:
pi = None
partial = bool(max_duration)
assert not (repair and partial)
mode = "partial" if partial else "full"
Expand Down Expand Up @@ -364,6 +369,8 @@ def check_object(obj):
obj_corrupted = False
check_object(obj)
objs_checked += 1
if pi:
pi.show(objs_checked)
if obj_corrupted:
objs_errors += 1
if repair:
Expand Down Expand Up @@ -393,10 +400,14 @@ def check_object(obj):
logger.info(f"Checkpointing at key {key}.")
self.store.store(LAST_KEY_CHECKED, key.encode())
if partial and now > t_start + max_duration:
if pi:
pi.finish()
logger.info(f"Finished partial repository check, last key checked is {key}.")
self.store.store(LAST_KEY_CHECKED, key.encode())
break
else:
if pi:
pi.finish()
logger.info("Finished repository check.")
try:
self.store.delete(LAST_KEY_CHECKED)
Expand All @@ -411,6 +422,8 @@ def check_object(obj):
)
except StoreObjectNotFound:
# it can be that there is no "data/" at all, then it crashes when iterating infos.
if pi:
pi.finish()
pass
logger.info(f"Checked {objs_checked} repository objects, {objs_errors} errors.")
if objs_errors == 0:
Expand Down