logarithmic progress bars woohoo

This commit is contained in:
Nick Sweeting 2020-10-31 07:56:05 -04:00
parent ac9e0e356d
commit b8bbb75f9c
2 changed files with 30 additions and 11 deletions

View file

@ -48,7 +48,7 @@ CONFIG_DEFAULTS: Dict[str, ConfigDefaultDict] = {
'SHELL_CONFIG': { 'SHELL_CONFIG': {
'IS_TTY': {'type': bool, 'default': lambda _: sys.stdout.isatty()}, 'IS_TTY': {'type': bool, 'default': lambda _: sys.stdout.isatty()},
'USE_COLOR': {'type': bool, 'default': lambda c: c['IS_TTY']}, 'USE_COLOR': {'type': bool, 'default': lambda c: c['IS_TTY']},
'SHOW_PROGRESS': {'type': bool, 'default': lambda c: False if platform.system() == 'Darwin' else c['IS_TTY']}, # TODO: remove this temporary hack once progress bars are fixed on macOS 'SHOW_PROGRESS': {'type': bool, 'default': lambda c: c['IS_TTY']},
'IN_DOCKER': {'type': bool, 'default': False}, 'IN_DOCKER': {'type': bool, 'default': False},
# TODO: 'SHOW_HINTS': {'type: bool, 'default': True}, # TODO: 'SHOW_HINTS': {'type: bool, 'default': True},
}, },

View file

@ -4,7 +4,9 @@ import re
import os import os
import sys import sys
import time import time
import platform
import argparse import argparse
from math import log
from multiprocessing import Process from multiprocessing import Process
from pathlib import Path from pathlib import Path
@ -99,10 +101,24 @@ class TimedProgress:
if self.SHOW_PROGRESS: if self.SHOW_PROGRESS:
# terminate if we havent already terminated # terminate if we havent already terminated
try: try:
# WARNING: HACKY
# I've spent over 15 hours trying to get rid of this stupid macOS-only
# intermittent (but harmeless) warning when the progress bars end sometimes
# Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
# BrokenPipeError: [Errno 32] Broken pipe
# In the end, this is the only thing I found that makes it
# happen slightly less often:
if platform.system() == 'Darwin':
time.sleep(0.1)
# kill the progress bar subprocess
self.p.terminate() self.p.terminate()
self.p.join() self.p.join()
self.p.close() self.p.close()
if platform.system() == 'Darwin':
time.sleep(0.1)
# clear whole terminal line # clear whole terminal line
try: try:
sys.stdout.write('\r{}{}\r'.format((' ' * TERM_WIDTH()), ANSI['reset'])) sys.stdout.write('\r{}{}\r'.format((' ' * TERM_WIDTH()), ANSI['reset']))
@ -128,17 +144,18 @@ def progress_bar(seconds: int, prefix: str='') -> None:
sys.stdout.write('\r\n') sys.stdout.write('\r\n')
sys.stdout.flush() sys.stdout.flush()
chunks = max_width - len(prefix) - 20 chunks = max_width - len(prefix) - 20
progress = s / chunks / seconds * 100 pct_complete = s / chunks / seconds * 100
bar_width = round(progress/(100/chunks)) log_pct = (log(pct_complete or 1, 10) / 2) * 100 # everyone likes faster progress bars ;)
bar_width = round(log_pct/(100/chunks))
last_width = max_width last_width = max_width
# ████████████████████ 0.9% (1/60sec) # ████████████████████ 0.9% (1/60sec)
sys.stdout.write('\r{0}{1}{2}{3} {4}% ({5}/{6}sec)'.format( sys.stdout.write('\r{0}{1}{2}{3} {4}% ({5}/{6}sec)'.format(
prefix, prefix,
ANSI['green'], ANSI['green' if pct_complete < 80 else 'lightyellow'],
(chunk * bar_width).ljust(chunks), (chunk * bar_width).ljust(chunks),
ANSI['reset'], ANSI['reset'],
round(progress, 1), round(pct_complete, 1),
round(s/chunks), round(s/chunks),
seconds, seconds,
)) ))
@ -146,7 +163,7 @@ def progress_bar(seconds: int, prefix: str='') -> None:
time.sleep(1 / chunks) time.sleep(1 / chunks)
# ██████████████████████████████████ 100.0% (60/60sec) # ██████████████████████████████████ 100.0% (60/60sec)
sys.stdout.write('\r{0}{1}{2}{3} {4}% ({5}/{6}sec)\n'.format( sys.stdout.write('\r{0}{1}{2}{3} {4}% ({5}/{6}sec)'.format(
prefix, prefix,
ANSI['red'], ANSI['red'],
chunk * chunks, chunk * chunks,
@ -156,6 +173,10 @@ def progress_bar(seconds: int, prefix: str='') -> None:
seconds, seconds,
)) ))
sys.stdout.flush() sys.stdout.flush()
# uncomment to have it disappear when it hits 100% instead of staying full red:
# time.sleep(0.5)
# sys.stdout.write('\r{}{}\r'.format((' ' * TERM_WIDTH()), ANSI['reset']))
# sys.stdout.flush()
except (KeyboardInterrupt, BrokenPipeError): except (KeyboardInterrupt, BrokenPipeError):
print() print()
pass pass
@ -242,7 +263,7 @@ def log_archiving_started(num_links: int, resume: Optional[float]=None):
**ANSI, **ANSI,
)) ))
else: else:
print('{green}[▶] [{}] Collecting content for {} Snapshots in archive...{reset}'.format( print('{green}[▶] [{}] Starting archiving of {} snapshots in index...{reset}'.format(
start_ts.strftime('%Y-%m-%d %H:%M:%S'), start_ts.strftime('%Y-%m-%d %H:%M:%S'),
num_links, num_links,
**ANSI, **ANSI,
@ -287,10 +308,8 @@ def log_archiving_finished(num_links: int):
print(' - {} links updated'.format(_LAST_RUN_STATS.succeeded + _LAST_RUN_STATS.failed)) print(' - {} links updated'.format(_LAST_RUN_STATS.succeeded + _LAST_RUN_STATS.failed))
print(' - {} links had errors'.format(_LAST_RUN_STATS.failed)) print(' - {} links had errors'.format(_LAST_RUN_STATS.failed))
print() print()
print(' {lightred}Hint:{reset} To view your archive index, run:'.format(**ANSI)) print(' {lightred}Hint:{reset} To manage your archive in a Web UI, run:'.format(**ANSI))
print(' archivebox server # then visit http://127.0.0.1:8000') print(' archivebox server 0.0.0.0:8000')
print(' Or run the built-in webserver:')
print(' archivebox server')
def log_link_archiving_started(link: "Link", link_dir: str, is_new: bool): def log_link_archiving_started(link: "Link", link_dir: str, is_new: bool):